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

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

Related

Is there a way to set a parameter from a stored procedure to a var query?

I'm working in a .net app specifically in a validation scripted in a stored procedure. This simple validation checks if there is already a policy with the same policy number that the user is trying to insert and throws an error message.
The problem that I'm facing is of the kind
Procedure or function 'pr_Policy_CheckPolicyNumber' expects parameter '#companyID', which was not supplied.
That's because I added a new field from the same PolicyBill table named (CompanyID).
My question is if it's there a way to set the #CompanyID value directly to the function and fix that error
The stored procedure is
CREATE OR ALTER PROCEDURE [dbo].[pr_Policy_CheckPolicyNumber]
(
#policyNumber VARCHAR(50) ,
#effectiveOn DATETIME ,
#companyID BIGINT
)
AS
IF ( ( SELECT COUNT(pb.policyBillID)
FROM PolicyBill pb
WHERE pb.policyNumber = #policyNumber
AND ( #effectiveOn >= pb.effectiveOn ) and (#effectiveOn <= pb.expiresOn )
AND pb.isActive = 1
AND pb.statusID = 56
AND pb.documentTypeID = 1
AND pb.companyID = #companyID
) > 0 )
BEGIN
PRINT N'The Policy already exists';
SELECT 1
END
ELSE
BEGIN
SELECT 0
END
and the Function is:
private bool CheckPolicyNumber()
{
var result = true;
var query = $"EXEC pr_Policy_CheckPolicyNumber '{txtPolicyNumber.Text}','{deEffectiveON.Date:yyyy/MM/dd}', '#companyID'";
var dt = AhkSqlHelper.ahkExecuteDataTable(query);
result = Convert.ToBoolean(Convert.ToInt16(dt.Rows[0][0].ToString()));
return result;
}
Any help would be appreciated.
Ad mentioned in the comment, the original code is fairly dangerous and you could be open to SQL injection attacks. The best solution is parameterize your command, something like this should do the trick:
using System.Data.SqlClient;
using System.Data;
string connString = "YourConnectionString";
string spName = "pr_Policy_CheckPolicyNumber";
private bool CheckPolicyNumber(string policyNumber, DateTime effectiveOn, long companyID)
{
using (SqlConnection conn = new SqlConnection(connString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(spName, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter parameter1 = new SqlParameter("#policyNumber ", policyNumber);
cmd.Parameters.Add(parameter1);
SqlParameter parameter2 = new SqlParameter("#effectiveOn ", effectiveOn);
cmd.Parameters.Add(parameter2);
SqlParameter parameter3 = new SqlParameter("#companyID ", companyID);
cmd.Parameters.Add(parameter3);
var result = cmd.ExecuteScalar()
return (bool)result;
}
}
}

Breaking up a static string sql command into the command portion and seperate parameters

I have a static string with parameters, being sent to an SQL Execute command.
The strings have the format of delete 'Name' from table where x = 1 and y = 2 or select * from table where x = 1 and y = 2.
My problem is that I need to break the string into parameters.
How do I break the strings so that I can pass the command with the parameters to a single functional with the least possible work?
I have only one function to fix and handle this problem.
From this:
protected object ExecuteScaler(string queryString)
{ OpenConnection(); }
DbCommand command = _provider.CreateCommand();
command.Connection = _connection; command.CommandText = queryString; command.CommandType = CommandType.Text; if (_useTransaction) {
command.Transaction = _transaction; }
try { returnValue = command.ExecuteScalar(); } ...
Can someone please give me an example?
When you build a sql-command like this:
// don't do this because of sql injection
sql = "SELECT * FROM MyTable WHERE Col2 = " + somevalue;
Where you "break out of" the string constant to place a value, that is the point where you want to use a parameter placeholder:
// safe from sql injection
sql = "SELECT * FROM MyTable WHERE Col2 = #somevalue";
And then you can supply the value for #somevalue using the Parameters collection.
When the query needs some fixed values, then it is probably OK to keep them in the string:
// the "type" never changes for this query:
sql = "SELECT * FROM MyTable WHERE type=1 and Col2 = #somevalue";

.NET SqlParameter constructor inconsistent?

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.

Retrieve value of a table and using the value in stored procedure

I'm beginner in SQL Server 2012; I need to generate a product ID in a stored procedure, I generated part of the ID in C#, that part of ID includes Industrialist ID and I pass this to my stored procedure. In the stored procedure I need the last product of my Industrialist number and save in to as SQL variable on my stored procedure. How can I do this?
There are many ways to pass items between SQL and C#, you could use an output parameter where you will populate the parameter within the stored procedure.
string variableName;
using (var conn = new SqlConnection("**connection string**"))
using (var cmd = new SqlCommand("storedProcedureName", conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("inputParameter", inputParameter);
var outputParameter = new SqlParameter(){
ParameterName="ParameterName"
,Direction = ParameterDirection.Output
,SqlDbType = SqlDbType.VarChar
,DbType = DbType.VarChar
};
conn.Open();
try
{
cmd.ExecuteNonQuery();
variableName = string.Format("{0}", outputParameter.Value);
}
catch{}
finally
{
conn.Close();
}
}
You could return the value using something along the lines of RETURN #returnValue in your procedure, or you could return it within a table.
string variableName;
using (var conn = new SqlConnection("**connection string**"))
using (var cmd = new SqlCommand("storedProcedureName", conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("inputParameter", inputParameter);
conn.Open();
try
{
using (var dbReader = cmd.ExecuteReader())
{
if (dbReader.Read())
{
variableName = string.Format("{0}", dbReader["ColumnName"]);
}
}
}
catch{}
finally
{
conn.Close();
}
}
A generic example of a stored procedure which might work:
CREATE PROC [dbo].[storedProcedureName]
#InputParameter VARCHAR
,#OutputParameter VARCHAR OUTPUT
AS BEGIN
DECLARE #insertedId INT;
BEGIN TRANSACTION
INSERT INTO TableName (...Column Names...)
VALUES (... Values...)
SET #insertedId = SCOPE_IDENTITY()
COMMIT
SELECT #OutputParameter = ColumnName
FROM TableName
WHERE IdColumnName = #insertedId
END
EDIT: Possibly more relevant:
CREATE PROC [dbo].[storedProcedureName]
#IndustrialistId INT
,#OutputParameter VARCHAR OUTPUT -- This might be an int, but it's unclear what you want
AS BEGIN
DECLARE #productId INT;
SELECT #productId = MAX(ProductId)
FROM Products
WHERE IndustrialistId = #IndustrialistId;
SET #OutputParameter = CONVERT(VARCHAR,#IndustrialistId) + '-' + CONVERT(VARCHAR,#productId)
END
If you were to provide some code it might be easier for someone to give you a more tailored response. None of the above code has been syntax checked etc. so should be considered more pseudo code but hopefully it gives you something to work with.

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());