Is there a way to add a parameter to my SqlCommand in a way that the engine will not complain if it's not used in my query?
I have about 50 parameters to include in my query but which parameters need to be included depends highly on the situation. I could easily delete 200 lines of code if i could just put them all on top and build my query after adding my params.
A very simple / dumb / wrong.. example (yes, the solution here is to add id to the else clause)
cmd.Parameters.Add("#id", SqlDbType.Int).Value = id;
cmd.Parameters.Add("#name", SqlDbType.nVarChar, 250).Value = name;
if(id == null) cmd.CommandText = "INSERT INTO tab (name) VALUES (#name)";
else cmd.CommandText = "UPDATE tab SET name = #name WHERE id = #id";
This returns the error:
System.Data.SqlClient.SqlException: The parameterized query '(#id,#name) ' expects the parameter '#id', which was not supplied
If it's not possible, a simple 'No' will suffice to be accepted as an answer..
Is there a way to add a parameter to my SqlCommand in a way that the engine will not complain if it's not used in my query?
ADO.NET does not complain if you add a parameter and do not use it. The error message you report is because you are trying to use a parameter that you didn't add - the opposite scenario. Most likely, id is null. Parameters with a value of null are not added - you need to use DBNull.Value:
cmd.Parameters.Add("#id", SqlDbType.Int).Value = ((object)id) ?? DBNull.Value;
cmd.Parameters.Add("#name", SqlDbType.nVarChar, 250)
.Value = ((object)name) ?? DBNull.Value;
Alternatively, tools like "dapper" will make this easy:
conn.Execute(sql, new { id, name }); // job done
Related
I would like to know the difference between these 2 notations.
First of all I have a stored procedure
CREATE PROCEDURE AddSomething( #zonename varchar(50), #desc varchar(255), #TheNewId int OUTPUT ) AS
BEGIN
INSERT INTO a_zone(zonename, descr) VALUES(#zonename, #desc)
SELECT #TheNewId = SCOPE_IDENTITY()
END
What is the difference if I add parameters in this manner
SqlCommand Cmd = new SqlCommand("AddSomething", oConn);
Cmd.CommandType = CommandType.StoredProcedure;
SqlParameter oParam1 = Cmd.Parameters.AddWithValue("#zonename", sName);
SqlParameter oParam2 = Cmd.Parameters.AddWithValue("#desc", description);
and
SqlCommand Cmd2 = new SqlCommand("AddSomething", oConn);
Cmd2.CommandType = CommandType.StoredProcedure;
cmd2.Parameters.Add("#zonename", SqlDbType.VarChar).Value = zonename.Text.Trim();
cmd2.Parameters.Add("#desc", SqlDbType.VarChar).Value = desc.Text.Trim();
Here are some explanations:
difference between command Add and AddWithValue
Dim cmd as new SqlCommand("SELECT * FROM MyTable WHERE MyDate>#TheDate",conn)
cmd.Parameters.Add("#TheDate",SqlDbType.DateTime).Value="2/1/2007"
vs
cmd.Parameters.AddWithValue("#TheDate","2/1/2007")
"Add forces the conversion from string to date as it goes into the parameter. AddWithValue would have simply passed the string on to the SQL Server.
When using Parameters.Add - the SqlDbType is known at compile time
When using Parameters.AddWithValue the method has to box and unbox the value to find out its type.
Additional benefits of the former is that Add is a bit more code safe
and will assist against SQL injection attacks , code safe in terms
that if you try to pass a value that doesn't match the SqlDb type
defined - the error will be caught in .Net code and you will not have
to wait for the round trip back.
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.parameters.aspx
http://msdn.microsoft.com/en-us/library/yy6y35y8.aspx
Edit:
example to get an Output-Parameter:
C#
cmd.Parameters.Add(new SqlParameter("#TheNewId", SqlDbType.Int, int.MaxValue));
cmd.Parameters("#TheNewId").Direction = ParameterDirection.Output;
cmd.ExecuteNonQuery();
int theNewID = (int)cmd.Parameters("#TheNewId").Value;
VB.Net
cmd.Parameters.Add(New SqlParameter("#TheNewId", SqlDbType.Int, Int32.MaxValue))
cmd.Parameters("#TheNewId").Direction = ParameterDirection.Output
cmd.ExecuteNonQuery()
Dim theNewID As Int32 = DirectCast(cmd.Parameters("#TheNewId").Value, Int32)
When you use AddWithValue, the datatype will be worked out (as best possible) based on the types of the variables passed to the method - assuming sName and description are string variables, the params will be passed in as NVARCHAR.
I personally prefer the 2nd approach, being explicit with the data types (plus I actually specify the sizes too) so that they are guaranteed to match the sproc definition and avoid any unexpected behaviour.
I'm trying to get a count of column records in a Sql database and show the result in a MessageBox.
This is my code:
public DataTable CheckIfNameExist(string name)
{
con = Connect();
cmd = new SqlCommand();
cmd.Connection = con;
cmd.CommandText = "spCheckIfNameExist";
cmd.Parameters.AddWithValue("#Name", SqlDbType.NVarChar).Value = name;
MessageBox.Show(name);
Int32 totalNames = (Int32) cmd.ExecuteScalar();
string tNames = totalNames.ToString();
MessageBox.Show(tNames);
}
And this is my sp:
#Name nvarchar(50) = null
As
Begin
SELECT COUNT(*) from OrdersSent where CustomerName LIKE #Name + '%'
End
Problem:
It always returns 0.
There are a couple of errors in your code:
You should write it as:
cmd.CommandText = "spCheckIfNameExist";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#Name", SqlDbType.NVarChar).Value = name;
First you need to tell the ADO engine that you are calling a stored procedure and not a simple command text, but you also need to use Add instead of AddWithValue to be precise on the type of the parameter passed to the SP. Your code creates a parameter int becase the second parameter of the AddWithValue is the Value of the parameter not the type.
You have a few problems in the c# code - the most important is probably this:
cmd.Parameters.AddWithValue("#Name", SqlDbType.NVarChar).Value = name;
Don't use AddWithValue. Use Add.
Also, you didn't specify the command type - the default is Text.
And you are using fields for SqlConnection and SqlCommand - which is also the wrong thing to do. You should create and dispose both of them inside each method you are using them.
A better version of your code would be this:
using(var con = new SqlConnection(ConnectionString))
{
using(var cmd = new SqlCommand("spCheckIfNameExist", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#Name", SqlDbType.NVarChar).Value = name;
con.Open();
var tNames = cmd.ExecuteScalar().ToString();
}
}
Another thing that puzzles me is why a method called CheckIfNameExist returns a DataTable. I would expect it to simply return a bool.
If you really only want to check if the name exists, you can do this better on both the SQL level and the c# level.
A better SQL would be something like this:
SELECT CAST(CASE WHEN EXISTS(
SELECT 1
FROM OrdersSent
WHERE CustomerName LIKE #Name + '%'
) THEN 1 ELSE 0 END AS bit)
And on the c# level, bit translates directly to bool, so the code can simple be this:
public bool CheckIfNameExist(string name)
{
using(var con = new SqlConnection(ConnectionString))
{
using(var cmd = new SqlCommand("spCheckIfNameExist", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#Name", SqlDbType.NVarChar).Value = name;
con.Open();
return (bool)cmd.ExecuteScalar();
}
}
}
And another note - you should avoid using the sp prefix for stored procedures.
Microsoft have reserved this prefix for built in system procedures.
For more information, read Aaron Bertrand's Is the sp_ prefix still a no-no?, where you'll see that the short answer to this question is "Yes".
The sp_ prefix does not mean what you think it does: most people think sp stands for "stored procedure" when in fact it means "special." Stored procedures (as well as tables and views) stored in master with an sp_ prefix are accessible from any database without a proper reference (assuming a local version does not exist). If the procedure is marked as a system object (using sp_MS_marksystemobject (an undocumented and unsupported system procedure that sets is_ms_shipped to 1), then the procedure in master will execute in the context of the calling database.
You need to specify the type of your command like this:
cmd.CommandText = "spCheckIfNameExist";
cmd.CommandType = CommandType.StoredProcedure;
See also:
What is the benefit of using CommandType.StoredProcedure versus using CommandType.Text?
Although specify the type directly and use the Value property is more better than AddWithValue:
cmd.Parameters.Add("#Name", SqlDbType.NVarChar).Value = name;
The following article could be also interesting:
https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/
How do I make it so that my query only update the data I want?
Here's the current code
string query = string.Format("update Customer set title='{0}',[Name]='{1}'",titleComboBox2.Text,nameTextBox2.Text,"where ID="+idTextBox+"");
Apparently the last part of the query isn't working. Why it is that?
Because you didn't use any index argument as {2} for your third argument which is WHERE part.
That's why your query will be contain only update Customer set title='{0}',[Name]='{1}' part this will be update for your all rows since it doesn't have any filter.
Fun fact, you could see this as query if you would debug your code.
But more important
You should always use parameterized queries. This kind of string concatenations are open for SQL Injection attacks.
Let's assume you use ADO.NET;
using(var con = new SqlConnection(conString))
using(var cmd = con.CreateCommand())
{
cmd.CommandText = #"update Customer set title = #title, [Name] = #name
where ID = #id";
cmd.Paramter.Add("#title", SqlDbType.NVarChar).Value = titleComboBox2.Text;
cmd.Paramter.Add("#name", SqlDbType.NVarChar).Value = nameTextBox2.Text;
cmd.Paramter.Add("#id", SqlDbType.Int).Value = int.Parse(idTextBox.Text);
// I assumed your column types.
con.Open();
cmd.ExecuteNonQuery();
}
Currently your query does not use WHERE clause, because it is ignored by string.Format. You have 3 placeholder parameters, and you are using only {0} and {1}, so WHERE part is never added to the SQL query. Change your query to include WHERE clause, e.g. like this:
string query = string.Format("update Customer set title='{0}',[Name]='{1}' {2}",titleComboBox2.Text,nameTextBox2.Text,"where ID="+idTextBox.Text+"");
However, there is one very serious flaw in your code - it is vulnerable to SQL injection attack. There are hundreds of articles about it online, make sure to read about what that is and how to update your code accordingly (hint - parametrize queries)
OK, nobody seems to know how to solve the problem I'm having looping through a cursor/result set for storage into a List, so I'm going to break it down into pieces and try to slog through it that way. So, first of all:
I add SQL Parameters to an OracleCommand object this way (works fine):
cmd.Parameters.Add("ABCID", _ABCID);
cmd.Parameters["ABCID"].Direction = ParameterDirection.Input;
cmd.Parameters["ABCID"].DbType = DbType.String;
IOW, when I add the param, I pass the name of the parameterized portion of the SQL ("ABCID" above) and a value to give it (_ABCID is a variable that has been assigned, let's say, "42").
However, when adding a Cursor (output) param, it seems to want, not a value (such as an initialized cursor object), but simply the data type:
cmd.Parameters.Add("cur", Devart.Data.Oracle.OracleDbType.Cursor);
cmd.Parameters["cur"].Direction = ParameterDirection.Output;
(I tried both ways, and neither one works, so...?)
Verily/thus, my question is: Is this really the correct way of declaring a Cursor parameter to be outputted back for traversal/access?
I'm using the brand new version of DevArt DotConnect components (6.80.332), VS 2010, .NET 4
Updated:
Here's the code in more context:
public void PopulateCurrentUserRoles(String AUserName, List<String> ACurrentUserRoles) {
_UserName = AUserName;
String query = "select roleid from ABCrole where ABCid = :ABCID";
Devart.Data.Oracle.OracleCommand cmd = new Devart.Data.Oracle.OracleCommand(query, con);
cmd.CommandType = CommandType.Text;
int _ABCID = GetABCIDForUserName();
cmd.Parameters.Add("cur", Devart.Data.Oracle.OracleDbType.Cursor);
cmd.Parameters["cur"].Direction = ParameterDirection.Output;
cmd.Parameters.Add("ABCID", _ABCID);
cmd.Parameters["ABCID"].Direction = ParameterDirection.Input;
cmd.Parameters["ABCID"].DbType = DbType.String;
//cmd.ExecuteNonQuery(); blows up: "illegal variable name/number"
//cmd.ExecuteCursor(); " "
//cmd.ExecuteReader(); " "
Devart.Data.Oracle.OracleCursor oraCursor =
(Devart.Data.Oracle.OracleCursor)cmd.Parameters["cur"].Value;
Devart.Data.Oracle.OracleDataReader odr = oraCursor.GetDataReader(); // "Object reference not set to an instance of an object"
while (odr.Read()) {
ACurrentUserRoles.Add(odr.GetString(0));
}
}
the following is from the Oracle Data Provider for .NET Developer's Guide. yes, I know, "Devart". Nonetheless, It suggests the following:
Be careful with your parameter typing declaration.
add that cursor/output parameter to the Parameters collection before
any others.
As a long shot... my guide shows a OracleDbType.RefCursor but not a OracleDbType.Cursor. If DevArt has RefCursor, try that. In visual studio, what type does .NET think that parameter is? This question is not as dumb as I used to think.
... On the other hand, if the parameter is set as an OracleDbType.Char type by setting the OracleDbType property, the output data is returned
as an OracleString type. If both DbType and OracleDbType properties
are set before the command execution, the last setting takes affect.
. . .
"An application should not bind a value for output parameters; it is
the responsibility of ODP.NET to create the value object and populate
the OracleParameter Value property with the object. When binding by
position (default) to a function, ODP.NET expects the return value to
be bound first, before any other parameters."
EDIT:
Based on #Clay's self-answer... so there is no parameter specified for the output, rather one simply does this: OracleDataReader odr = cmd.ExecuteReader();
Straight from the horse's mouth (the DevArt folks):
_UserName = AUserName;
// From the DevArtisans:
String query = "select roleid from ABCrole where ABCid = :ABCID";
Devart.Data.Oracle.OracleCommand cmd = new Devart.Data.Oracle.OracleCommand(query, con);
cmd.CommandType = CommandType.Text;
int _ABCID = GetABCIDForUserName();
cmd.Parameters.Add("ABCID", _ABCID);
cmd.Parameters["ABCID"].Direction = ParameterDirection.Input;
cmd.Parameters["ABCID"].DbType = DbType.String;
Devart.Data.Oracle.OracleDataReader odr = cmd.ExecuteReader();
while (odr.Read()) {
ACurrentUserRoles.Add(odr.GetString(0));
}
To quote Casey and the Sonshine Banned, "That's the way, Uh huh Uh huh, I like it, Uh huh Uh huh"; actually, I can't stand that crap, but I do kind of relate to that sentiment right about now.
Since the OleDbParameter does not use named parameters (due to its nature),
why is it that the .NET OleDbParameter class expects a name? (string parametername ...)
All constructors require a parameter name, and I'm never sure what name to give it; my name is ok? or my grandmothers name?
Although the OleDb/Odbc providers use positional parameters instead of named parameters -
the parameters will need to be identified in some way inside the OleDbParameter collection should you need to reference them.
importantly when the parameterised sql statement is constructed, a variable for each parameter is declared and assigned it's respective value. and then used in the sql statement that is executed.
Take for example:
string sql = "SELECT * FROM MyTable WHERE Field = ?";
OleDbCommand cmd = new OleDbCommmand(sql, sqlConnection);
cmd.Parameters.Add("#FieldValue", OleDbType.Int32, 42);
OleDbDataReader dr = cmd.ExecuteReader();
The SQL executed would be something like (or close to I think):
DECLARE #FieldValue INT;
SET #FieldValue = 42;
SELECT * FROM MyTable WHERE Field = #FieldValue
You would be advised to use a parameter name that best matches the name of the column being operated on.
Just like everything else in programming, name it something meaningful to your context! (name, orderid, city, etc).
You use the names to access the parameters collection by name while in your c# code:
OleDbCommand command = new OleDbCommand();
...//Add your parameters with names, then reference them later if you need to:
command.Parameters["name"].Value = "your name or your grandmothers name here";
This is also useful when you use OUT parameters to extract the value after you execute your statement:
OleDbParameter outParam = new OleDbParameter();
outParam.Direction = ParameterDirection.Output;
outParam.DbType = DbType.Date;
outParam.ParameterName = "outParam";
command.Parameters.Add(outParam);
command.ExecuteNonQuery();
DateTime outParam = Convert.ToDateTime(command.Parameters["outParam"].Value);
There are interfaces and factories to provide a degree of independence of the underlying provider for data access - IDataParameter, DbParameter, DbProviderFactory etc.
To support this, all parameters can be named, even when the name is not used by the underlying provider.