.NET SqlParameter constructor inconsistent? - sql

Can anyone tell me what is going on in this function??
In the following code snippet, user.Id = 0, id.Value = 0 and id.SqlDbType = Int.. as expected since user.Id is an int field.
However, error.Value = null and error.SqlDbType = BigInt. What gives? If I use non-zero it detects an int and the correct value.
Note: the Value properties are the same before and after declaring the parameter direction.
public static long InsertUpdate(User user) {
SqlParameter id = new SqlParameter("#id", user.Id);
id.Direction = ParameterDirection.InputOutput;
cmd.Parameters.Add(id);
SqlParameter error = new SqlParameter("#error_code", 0);
error.Direction = ParameterDirection.Output;
cmd.Parameters.Add(error);
.... other stuff
}
As well, if #SET #error_Code = 0 in the sproc, error.Value = NULL and error.SqlDbType = NVarChar AFTER the procedure runs. If I set it to an integer I get an Int type.
UPDATE:
After specifying SqlDbType.Int the parameter now has the correct SqlDbType before and after the command... however the stored procedure is still setting #error_code = null when I in fact set it to 0.
UPDATE:
When the sproc executes the SELECT statement the #error_code parameter is always returned as null, regardless of when or not it has been set... this only happens when there's a select statement...
Here is the procedure to reproduce:
ALTER PROCEDURE [dbo].[usp_user_insert_v1]
#username VARCHAR(255),
#password VARCHAR(255),
#gender CHAR(1),
#birthday DATETIME,
#error_code INT OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #default_dt DATETIME
EXEC #default_dt = uf_get_default_date
DECLARE #dt DATETIME = GETUTCDATE()
INSERT INTO users(username, password, gender, birthday, create_dt, last_login_dt, update_dt, deleted)
VALUES(#username, #password, #gender, #birthday, #dt, #default_dt, #default_dt, 0)
SELECT * FROM users WHERE id = SCOPE_IDENTITY()
SET #error_code = 3
RETURN
END
SOLUTION?
http://forums.asp.net/t/1208409.aspx?Interesting+problem+with+getting+OUTPUT+parameters+from+SQL+Server+using+C+
Found this link on the ASP forums... apparently you can't read the output parameter until you have read all the results from the SqlDataReader... very unfortunate for me since I decide whether or not I even WANT to read the results based on the output param...

From SqlParameter.Value on MSDN
For output and return value parameters, the value is set on completion of the SqlCommand
i.e. I wouldn't rely on type inference to set the return type implicitly.
I would explicitly set the type of the output parameter:
var error = new SqlParameter("#error_code", SqlDbType.Int)
{
Direction = ParameterDirection.Output
};
Edit
After some reflection of SqlParameter:
The BigInt is easy to explain - it is the default SqlDbType, and the SqlParameter(string parameterName, object value) ctor doesn't overwrite this value.
public enum SqlDbType
{
BigInt = 0,
...
Re: #error_code is returned as NULL
The only thing I can think of is that the PROC fails to complete cleanly. Try moving the SET #error_code = 0 above the EXEC #default_dt = uf_get_default_date ?
Edit
Confirmed, #Damien's point is correct
SqlParameter error = new SqlParameter("#error_code", 0);
Actually calls this ctor:
public SqlParameter(string parameterName, SqlDbType dbType)
whereas
SqlParameter error = new SqlParameter("#error_code", 1234);
calls
public SqlParameter(string parameterName, object value)
Reason : 0 is implicitly castable to enum.

Both of the current answers are slightly incorrect because they're based on the assumption that the constructor being called for your error object is the (string,object) one. This is not the case. A literal 0 can be converted to any enum type1, and such a conversion would be preferred over a conversion to object. So the constructor being called is the (string,SqlDbType) constructor.
So the type is set to BigInt because that's the 0 value for the SqlDbType enumeration, and the Value is null because you have no code that attempts to set the value.
SqlParameter error = new SqlParameter("#error_code", (object)0);
should cause it to select the correct overload.
Demo:
using System;
using System.Data;
namespace ConsoleApplication
{
internal class Program
{
private static void Main()
{
var a = new ABC("ignore", 0);
var b = new ABC("ignore", (object)0);
var c = new ABC("ignore", 1);
int i = 0;
var d = new ABC("ignore", i);
Console.ReadLine();
}
}
public class ABC
{
public ABC(string ignore, object value)
{
Console.WriteLine("Object");
}
public ABC(string ignore, SqlDbType value)
{
Console.WriteLine("SqlDbType");
}
}
}
Prints:
SqlDbType
Object
Object
Object
1From the C# Language specification, version 5, section 1.10 (that is, just in the introduction to the language, not buried deep down in the language lawyery bits):
In order for the default value of an enum type to be easily available, the literal 0 implicitly converts to any enum type. Thus, the following is permitted.
Color c = 0;
I'd have also thought this important enough to be in the Language Reference on MSDN but haven't found a definitive source yet.

Well, it looks like the most reliable way of doing it is by using this overload:
SqlParameter error = new SqlParameter("#error_code", SqlDBType.Int);
error.Value = 0;
The overload you're using takes an object as a parameter, and for some reason which I can't divine, it's not picking the right type.

Related

How do I retrieve scope identity with ExecuteNonQuery?

My project is using .NET Core 3.1 and I have my stored procedures executing in my repository class. I want to insert and return the scope identity(the id of the record that just inserted UserNumber) so I can use it for another stored proc within this same method. The problem I have here is that parameters[1].Value value is returning zero.
Here is an abbreviation of my stored proc:
ALTER PROCEDURE [dbo].[InsertUser]
#iUserNumber int OUTPUT,
As
INSERT dbo.tblUser (
CreatedBy
)
VALUES (#LoginUserId)
IF ##ERROR <> 0 GOTO ERRHANDLER
SET #UserNumber = SCOPE_IDENTITY() /** this is the primary Key **/
RETURN(#UserNumber)
Here is a sample of my repository
public int InsertUsers(int LoginUserId, int UserNumber)
{
SqlParameter[] parameters = new List<SqlParameter>()
{
_dbContext.CreateSqlParameter(StoredProcedureConstants.LoginUserId,SqlDbType.Int,LoginUserId.ToSafeInt()),
_dbContext.CreateSqlParameter(StoredProcedureConstants.UserNumber,SqlDbType.Int,UserNumber.ToSafeInt())
}.ToArray();
var intResult = _dbContext.ExecuteNonQuery(StoredProcedureConstants.InsertUsers, parameters);
var result2 = parameters[1].Value; //This comes back as zero
How do I assign the scope identity to result2?
Should be something like:
CREATE OR ALTER PROCEDURE [dbo].[InsertUser]
#LoginUserId int,
#iUserNumber int OUTPUT
As
INSERT dbo.tblUser (CreatedBy)
VALUES (#LoginUserId)
SET #iUserNumber = SCOPE_IDENTITY() /** this is the primary Key **/
and
SqlParameter[] parameters = new List<SqlParameter>()
{
_dbContext.CreateSqlParameter("#LoginuserId",SqlDbType.Int,LoginUserId),
_dbContext.CreateSqlParameter("#iUserNumber",SqlDbType.Int)
}.ToArray();
parameters[1].Direction = ParameterDirection.Output;
_dbContext.ExecuteNonQuery(StoredProcedureConstants.InsertUsers, parameters);
var result2 = parameters[1].Value;

Sending datetime value to a stored procedure

I want to send just date to a stored procedure and I wrote this C# code:
string[] pr = { "/" };
string[] s = txtStartDate.Text.Split(pr, StringSplitOptions.None);
term.Start_date = new DateTime(Convert.ToInt32(s[0]), Convert.ToInt32(s[1]), Convert.ToInt32(s[2])).Date;
s = txtEndDate.Text.Split(pr, StringSplitOptions.None);
term.End_date = new DateTime(Convert.ToInt32(s[0]),Convert.ToInt32(s[1]),Convert.ToInt32(s[2])).Date;
and I send it to the stored procedure like this:
public bool AddNewTerm(Term term)
{
SqlParameter[] parameters = new SqlParameter[]
{
new SqlParameter ("#termName",term.TermName),
new SqlParameter ("#start_date",term.Start_date),
new SqlParameter ("#end_date",term.End_date)
};
return SqlDBHelper.ExecuteNonQuery("AddNewTerm", CommandType.StoredProcedure, parameters);
}
but when it goes to the stored procedure say this:
SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.
I see other topic but they cant help me
This is the stored procedure code:
ALTER PROCEDURE dbo.AddNewTerm
(
#termName varchar(50),
#start_date date,
#end_date date
)
AS
insert into term(termName, start_date, end_date)
values(#termName, #start_date, #end_date)
RETURN
Where is the problem?
I find my answer by changing my way
I used PersianCalendar Class and store it varchar.
The error message talks about a lower limit of 1/1/1753, so, supposing that you have parsed correctly your inputs (30/6/1390) the message seems clearly indicate that the two column start_date and end_dateare of type datetimethat has a lower limit of 1/1/1753.
So, to store a date with year less than 1753 you need a datetime2 or date column that have a lower limit of 1/1/0001
Here a quick reference for the two datatypes
There is another problem in your code. You add the parameters to the array without specyfing their SqlDbType and in this way the Date are added as DateTime parameters that of course cannot accept a value below 1/1/1753. A workaround for your specific code could be.
public bool AddNewTerm(Term term)
{
List<SqlParameter> parameters = new List<SqlParameter>()
{
new SqlParameter("#termName",SqlDBType.VarChar, 50) {Value = term.TermName},
new SqlParameter("#start_date",SqlDBType.DateTime2, 0) {Value = term.Start_Date},
new SqlParameter("#end_date",SqlDBType.DateTime2, 0) {Value = term.End_Date},
};
return SqlDBHelper.ExecuteNonQuery("AddNewTerm", CommandType.StoredProcedure, parameters.ToArray());
}

Implicit conversion from data type nvarchar to varbinary(max) is not allowed

I get this exception when I try to insert a DBNull.Value into a nullable varbinary(max) field:
Implicit conversion from data type nvarchar to varbinary(max) is not allowed. Use the CONVERT function to run this query.
This is my code:
insertCMD.Parameters.AddWithValue("#ErrorScreenshot", SqlDbType.VarBinary).Value = DBNull.Value;
I know there exist duplicate questions on SO, but I do NOT use any String like the others do.
What do I wrong?
UPDATE:
using (var insertCMD = new SqlCommand("INSERT INTO TestplanTeststep (TeststepId,TestplanId,CreatedAt,ErrorText,ErrorScreenshot,TestState) VALUES (#TeststepId, #TestplanId,#CreatedAt,#ErrorText,#ErrorScreenshot,#TestState)", con))
{
var p1 = insertCMD.Parameters.Add("#TeststepId", SqlDbType.Int);
var p2 = insertCMD.Parameters.Add("#CreatedAt", SqlDbType.DateTime);
insertCMD.Parameters.AddWithValue("#TestplanId", testplan.Id);
insertCMD.Parameters.AddWithValue("#ErrorText", (object) DBNull.Value);
insertCMD.Parameters.AddWithValue("#ErrorScreenshot", (object) DBNull.Value);
insertCMD.Parameters.AddWithValue("#TestState", (int)Teststep.TeststepTestState.Untested);
foreach (Teststep step in teststeps)
{
p1.Value = step.Id;
p2.Value = step.CreatedAt;
insertCMD.ExecuteNonQuery();
}
}
I had the same problem while insertion DBNull.Value for a Varbinary(Max) column. After Googling I found a solution that may help you as well:
You need to set size -1 which means Max length for varbinary column when adding your sql parameter:
this.cmd.Parameters.Add("#Photo", SqlDbType.VarBinary, -1).Value = DBNull.Value;
So in your case:
insertCMD.Parameters.Add("#ErrorScreenshot", SqlDbType.VarBinary,-1).Value = DBNull.Value;
Why not change your SQL to:
INSERT INTO TestplanTeststep
(TeststepId,TestplanId,CreatedAt,ErrorText,ErrorScreenshot,TestState)
VALUES
(#TeststepId, #TestplanId,#CreatedAt,NULL,NULL,#TestState)
or just
INSERT INTO TestplanTeststep
(TeststepId,TestplanId,CreatedAt,TestState)
VALUES
(#TeststepId, #TestplanId,#CreatedAt,#TestState)
...and omit the two parameters?
If it's always NULL, that will have the same effect.
Otherwise, try it in two lines:
var binary1 = insertCMD.Parameters.Add("#ErrorScreenshot", SqlDbType.VarBinary, -1);
binary1.Value = DBNull.Value;
Otherwise, in your original SQL insert statement, you're not defining the parameter type but passing in varbinary, hence the error.

ADO.NET: Specified cast is not valid

I get a specified cast not valid error when I execute a stored proc like this:
return (int)comm.ExecuteScalar();
When I execute it in SQL Server it returns 1, so I know my proc is working.
What is wrong with my cast?
Updated:
public static int IsPresent(string userName, int inTime)
{
SqlConnection connObj = new SqlConnection();
connObj.ConnectionString = Util.SQLConct();
connObj.Open();
SqlCommand comm = new SqlCommand("usp_IsUserLocked", connObj);
comm.CommandType = CommandType.StoredProcedure;
comm.Parameters.Add(new SqlParameter("#Username", userName));
comm.Parameters.Add(new SqlParameter("#InTime", inTime));
return (int)comm.ExecuteScalar();
}
Thanks in advance
It is possible that your method returns double or int64 or any other type which cannot be implicitly casted to (int). Use Convert.ToInt32 to ensure that your method returns the right type.
using System;
...
return Convert.ToInt32(comm.ExecuteScalar());

How do you specify 'DEFAULT' as a SQL parameter value in ADO.NET?

I have a parameterized SQL query targetted for SQL2005 which is dynamically created in code, so I used the ADO.NET SqlParameter class to add sql parameters to SqlCommand.
In the aforementioned SQL I select from a Table Valued Function with has defaults. I want my dynamic sql to sometimes specify a value for these default parameters, and other times I want to specify that the SQL DEFAULT - as defined in the Table Valued Function - should be used.
To keep the code clean I didn't want to dynamically add the SQL DEFAULT keyword and parameterize it when a non-default is to be used, I just wanted to set DEFAULT as the value of my SQLParameter.
Can I? What is best practice in such an instance?
SQL query parameters take the place of literal values only.
You can't send an SQL keyword as the value of a parameter, just as you cannot send a table identifier, column identifier, list of values (e.g. for an IN predicate), or an expression. The value of the parameter is always interpreted as a literal value, as if you had included a quoted string literal or a numeric literal in your query.
Sorry, but you have to include an SQL keyword as part of the SQL query before you prepare that query.
AFAIK, the only way to tell SQL Server to use a default value is via the DEFAULT keyword or to exclude it from parameter list. That means that the use of the DEFAULT keyword must be in your parameterized SQL Statement. So, something like:
Select ...
From dbo.udf_Foo( DEFAULT, #Param2, #Param3, DEFAULT, .... )
I suppose another approach would be to query the system catalogs for the actual value of the various DEFAULT values and determine whether to set the SqlParameter to the default value that way, but that requires a convoluted second query to get the default values.
If you have the following function (for example):
CREATE FUNCTION dbo.UFN_SAMPLE_FUNCTION
(
#Param1 nvarchar(10),
#Param2 int = NULL
)
RETURNS TABLE
AS
RETURN
SELECT #Param1 AS Col1, #Param2 AS Col2;
GO
Then you can use it the following way (option 1):
SELECT * FROM dbo.UFN_SAMPLE_FUNCTION ('ABC', DEFAULT);
which is correct way and you get the following result:
Col1 Col2
---------- -----------
ABC NULL
But if you try to use parametrized query (option 2):
exec sp_executesql N'SELECT * FROM dbo.UFN_SAMPLE_FUNCTION (#P1, #P2)',N'#P1 nvarchar(10),#P2 int',#P1=N'abc',#P2=default;
you will get an error:
Msg 8178, Level 16, State 1, Line 0
The parameterized query '(#P1 nvarchar(10),#P2 int)SELECT * FROM dbo.UFN_SAMPLE_FUNCTION' expects the parameter '#P2', which was not supplied.
If you have the following .net code:
public void RunTVF(string param1, int? param2)
{
using (SqlConnection con = GetProdConection())
{
using (var cmd = new SqlCommand("SELECT * FROM dbo.UFN_SAMPLE_FUNCTION (#P1, #P2)", con))
{
cmd.CommandType = CommandType.Text;
var param = new SqlParameter
{
ParameterName = "#P1",
SqlDbType = SqlDbType.NVarChar,
Size = 10 ,
Value = param1
};
cmd.Parameters.Add(param);
param = new SqlParameter
{
ParameterName = "#P2",
SqlDbType = SqlDbType.Int,
Value = param2
};
cmd.Parameters.Add(param);
cmd.Connection.Open();
using (IDataReader dataReader = cmd.ExecuteReader())
{
//...
}
}
}
}
then, in case param2 = null as Jack suggested above, the script produced by the code will be identical to the option 2 and will result to the same error. So you cannot use NULL in this case.You cannot set DEFAULT as the value of SQLParameter either.
What you can do is to create a stored procedure to wrap the call to your funcion and move your default value from the function to the SP. Example:
CREATE PROCEDURE dbo.USP_SAMPLE_PROCEDURE
(
#Param1 nvarchar(10),
#Param2 int = NULL, --DEFAULT value now is here (remove it from the function)
#Statement nvarchar(max)
)
AS
BEGIN
SET NOCOUNT ON;
EXEC sp_executesql #Statement,N'#P1 nvarchar(10),#P2 int',#P1=#Param1,#P2=#Param2;
END
The .NET code will look the following way:
public void RunWrapper(string param1, int? param2)
{
using (SqlConnection con = GetProdConection())
{
using (var cmd = new SqlCommand("USP_SAMPLE_PROCEDURE", con))
{
cmd.CommandType = CommandType.StoredProcedure;
var param = new SqlParameter
{
ParameterName = "#Param1",
SqlDbType = SqlDbType.NVarChar,
Size = 10,
Value = param1
};
cmd.Parameters.Add(param);
param = new SqlParameter
{
ParameterName = "#Param2",
SqlDbType = SqlDbType.Int,
Value = param2
};
cmd.Parameters.Add(param);
param = new SqlParameter
{
ParameterName = "#Statement",
SqlDbType = SqlDbType.NVarChar,
Size = -1, //-1 used in case you need to specify nvarchar(MAX)
Value = "SELECT * FROM dbo.UFN_SAMPLE_FUNCTION (#P1, #P2)"
};
cmd.Parameters.Add(param);
cmd.Connection.Open();
using (IDataReader dataReader = cmd.ExecuteReader())
{
//...
}
}
}
}
In this case null as a value for the param2 will be translated to the correct DEFAULT and the following script will be produced:
exec USP_SAMPLE_PROCEDURE #Param1=N'ABC',#Param2=default,#Statement=N'SELECT * FROM dbo.UFN_SAMPLE_FUNCTION (#P1, #P2)'
which will give you the following result:
Col1 Col2
---------- -----------
ABC NULL
I am not sure that this is the best practice. This is just the work-around.
Though you can't set an SQL keyword as the value of a parameter, you could in this case go and get the DEFAULT VALUE.
SELECT COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'table_name' AND COLUMN_NAME = 'column_name'"
if you pass a dot net null value as the parameter value it will use sql DEFAULT
if you pass a dot net DBNull.Value it will use sql NULL