Related
I have a table which contains a single column "CODE_CAR".
I want to populate this table with list of string as below :
_Connection.Open();
var lst_code_cars = new List<string> { "YH_21", "NM_00", "BAR_N178" };
string cmdText = "INSERT INTO Y157.CARS_IDENDITY(CODE_CAR) VALUES ("+lst_code_cars+")";
MySqlCommand cmd = new MySqlCommand(cmdText, _Connection);
foreach (string cars_code in lst_code_cars )
{
cmd.Parameters.AddWithValue("#"+cars_code , cars_code );
cmd.ExecuteNonQuery();
}
_Connection.Close();
When i try this code, i get error : MySqlException : Parameter '#NM_00' must be defined.
The error tells you exactly what is wrong. You did not define a parameter, you concatenated the value directly into the query string. So don't do that.
_Connection.Open();
var lst_code_cars = new List<string> { "YH_21", "NM_00", "BAR_N178" };
string cmdText = "INSERT INTO Y157.CARS_IDENDITY(CODE_CAR) VALUES (#CarCode)";
MySqlCommand cmd = new MySqlCommand(cmdText, _Connection);
foreach (string cars_code in lst_code_cars )
{
cmd.Parameters.Clear();
cmd.Parameters.AddWithValue("#CarCode" , cars_code );
cmd.ExecuteNonQuery();
}
_Connection.Close();
You'll also need to clear the parameters if you mean to re-add them each loop. Either that or add the parameter once and then edit the value in the loop instead of adding a new parameter each time.
Ideally, though, you could write this as a batch insert.
I am trying to pass array parameter to SQL commnd in C# like below, but it does not work. Does anyone meet it before?
string sqlCommand = "SELECT * from TableA WHERE Age IN (#Age)";
SqlConnection sqlCon = new SqlConnection(connectString);
SqlCommand sqlComm = new SqlCommand();
sqlComm.Connection = sqlCon;
sqlComm.CommandType = System.Data.CommandType.Text;
sqlComm.CommandText = sqlCommand;
sqlComm.CommandTimeout = 300;
sqlComm.Parameters.Add("#Age", SqlDbType.NVarChar);
StringBuilder sb = new StringBuilder();
foreach (ListItem item in ddlAge.Items)
{
if (item.Selected)
{
sb.Append(item.Text + ",");
}
}
sqlComm.Parameters["#Age"].Value = sb.ToString().TrimEnd(',');
You will need to add the values in the array one at a time.
var parameters = new string[items.Length];
var cmd = new SqlCommand();
for (int i = 0; i < items.Length; i++)
{
parameters[i] = string.Format("#Age{0}", i);
cmd.Parameters.AddWithValue(parameters[i], items[i]);
}
cmd.CommandText = string.Format("SELECT * from TableA WHERE Age IN ({0})", string.Join(", ", parameters));
cmd.Connection = new SqlConnection(connStr);
UPDATE: Here is an extended and reusable solution that uses Adam's answer along with his suggested edit. I improved it a bit and made it an extension method to make it even easier to call.
public static class SqlCommandExt
{
/// <summary>
/// This will add an array of parameters to a SqlCommand. This is used for an IN statement.
/// Use the returned value for the IN part of your SQL call. (i.e. SELECT * FROM table WHERE field IN ({paramNameRoot}))
/// </summary>
/// <param name="cmd">The SqlCommand object to add parameters to.</param>
/// <param name="paramNameRoot">What the parameter should be named followed by a unique value for each value. This value surrounded by {} in the CommandText will be replaced.</param>
/// <param name="values">The array of strings that need to be added as parameters.</param>
/// <param name="dbType">One of the System.Data.SqlDbType values. If null, determines type based on T.</param>
/// <param name="size">The maximum size, in bytes, of the data within the column. The default value is inferred from the parameter value.</param>
public static SqlParameter[] AddArrayParameters<T>(this SqlCommand cmd, string paramNameRoot, IEnumerable<T> values, SqlDbType? dbType = null, int? size = null)
{
/* An array cannot be simply added as a parameter to a SqlCommand so we need to loop through things and add it manually.
* Each item in the array will end up being it's own SqlParameter so the return value for this must be used as part of the
* IN statement in the CommandText.
*/
var parameters = new List<SqlParameter>();
var parameterNames = new List<string>();
var paramNbr = 1;
foreach (var value in values)
{
var paramName = string.Format("#{0}{1}", paramNameRoot, paramNbr++);
parameterNames.Add(paramName);
SqlParameter p = new SqlParameter(paramName, value);
if (dbType.HasValue)
p.SqlDbType = dbType.Value;
if (size.HasValue)
p.Size = size.Value;
cmd.Parameters.Add(p);
parameters.Add(p);
}
cmd.CommandText = cmd.CommandText.Replace("{" + paramNameRoot + "}", string.Join(",", parameterNames));
return parameters.ToArray();
}
}
It is called like this...
var cmd = new SqlCommand("SELECT * FROM TableA WHERE Age IN ({Age})");
cmd.AddArrayParameters("Age", new int[] { 1, 2, 3 });
Notice the "{Age}" in the sql statement is the same as the parameter name we are sending to AddArrayParameters. AddArrayParameters will replace the value with the correct parameters.
I wanted to expand on the answer that Brian contributed to make this easily usable in other places.
/// <summary>
/// This will add an array of parameters to a SqlCommand. This is used for an IN statement.
/// Use the returned value for the IN part of your SQL call. (i.e. SELECT * FROM table WHERE field IN (returnValue))
/// </summary>
/// <param name="sqlCommand">The SqlCommand object to add parameters to.</param>
/// <param name="array">The array of strings that need to be added as parameters.</param>
/// <param name="paramName">What the parameter should be named.</param>
protected string AddArrayParameters(SqlCommand sqlCommand, string[] array, string paramName)
{
/* An array cannot be simply added as a parameter to a SqlCommand so we need to loop through things and add it manually.
* Each item in the array will end up being it's own SqlParameter so the return value for this must be used as part of the
* IN statement in the CommandText.
*/
var parameters = new string[array.Length];
for (int i = 0; i < array.Length; i++)
{
parameters[i] = string.Format("#{0}{1}", paramName, i);
sqlCommand.Parameters.AddWithValue(parameters[i], array[i]);
}
return string.Join(", ", parameters);
}
You can use this new function as follows:
SqlCommand cmd = new SqlCommand();
string ageParameters = AddArrayParameters(cmd, agesArray, "Age");
sql = string.Format("SELECT * FROM TableA WHERE Age IN ({0})", ageParameters);
cmd.CommandText = sql;
Edit:
Here is a generic variation that works with an array of values of any type and is usable as an extension method:
public static class Extensions
{
public static void AddArrayParameters<T>(this SqlCommand cmd, string name, IEnumerable<T> values)
{
name = name.StartsWith("#") ? name : "#" + name;
var names = string.Join(", ", values.Select((value, i) => {
var paramName = name + i;
cmd.Parameters.AddWithValue(paramName, value);
return paramName;
}));
cmd.CommandText = cmd.CommandText.Replace(name, names);
}
}
You can then use this extension method as follows:
var ageList = new List<int> { 1, 3, 5, 7, 9, 11 };
var cmd = new SqlCommand();
cmd.CommandText = "SELECT * FROM MyTable WHERE Age IN (#Age)";
cmd.AddArrayParameters("Age", ageList);
Make sure you set the CommandText before calling AddArrayParameters.
Also make sure your parameter name won't partially match anything else in your statement (i.e. #AgeOfChild)
If you can use a tool like "dapper", this can be simply:
int[] ages = { 20, 21, 22 }; // could be any common list-like type
var rows = connection.Query<YourType>("SELECT * from TableA WHERE Age IN #ages",
new { ages }).ToList();
Dapper will handle unwrapping this to individual parameters for you.
If you are using MS SQL Server 2008 and above you can use table-valued parameters like described here
http://www.sommarskog.se/arrays-in-sql-2008.html.
1. Create a table type for each parameter type you will be using
The following command creates a table type for integers:
create type int32_id_list as table (id int not null primary key)
2. Implement helper methods
public static SqlCommand AddParameter<T>(this SqlCommand command, string name, IEnumerable<T> ids)
{
var parameter = command.CreateParameter();
parameter.ParameterName = name;
parameter.TypeName = typeof(T).Name.ToLowerInvariant() + "_id_list";
parameter.SqlDbType = SqlDbType.Structured;
parameter.Direction = ParameterDirection.Input;
parameter.Value = CreateIdList(ids);
command.Parameters.Add(parameter);
return command;
}
private static DataTable CreateIdList<T>(IEnumerable<T> ids)
{
var table = new DataTable();
table.Columns.Add("id", typeof (T));
foreach (var id in ids)
{
table.Rows.Add(id);
}
return table;
}
3. Use it like this
cmd.CommandText = "select * from TableA where Age in (select id from #age)";
cmd.AddParameter("#age", new [] {1,2,3,4,5});
Since there is a method on
SqlCommand.Parameters.AddWithValue(parameterName, value)
it might be more convenient to create a method accepting a parameter (name) to replace and a list of values. It is not on the Parameters level (like AddWithValue) but on command itself so it's better to call it AddParametersWithValues and not just AddWithValues:
query:
SELECT * from TableA WHERE Age IN (#age)
usage:
sqlCommand.AddParametersWithValues("#age", 1, 2, 3);
the extension method:
public static class SqlCommandExtensions
{
public static void AddParametersWithValues<T>(this SqlCommand cmd, string parameterName, params T[] values)
{
var parameterNames = new List<string>();
for(int i = 0; i < values.Count(); i++)
{
var paramName = #"#param" + i;
cmd.Parameters.AddWithValue(paramName, values.ElementAt(i));
parameterNames.Add(paramName);
}
cmd.CommandText = cmd.CommandText.Replace(parameterName, string.Join(",", parameterNames));
}
}
I want to propose another way, how to solve limitation with IN operator.
For example we have following query
select *
from Users U
WHERE U.ID in (#ids)
We want to pass several IDs to filter users. Unfortunately it is not possible to do with C# in easy way. But I have fount workaround for this by using "string_split" function. We need to rewrite a bit our query to following.
declare #ids nvarchar(max) = '1,2,3'
SELECT *
FROM Users as U
CROSS APPLY string_split(#ids, ',') as UIDS
WHERE U.ID = UIDS.value
Now we can easily pass one parameter enumeration of values separated by comma.
Passing an array of items as a collapsed parameter to the WHERE..IN clause will fail since query will take form of WHERE Age IN ("11, 13, 14, 16").
But you can pass your parameter as an array serialized to XML or JSON:
Using nodes() method:
StringBuilder sb = new StringBuilder();
foreach (ListItem item in ddlAge.Items)
if (item.Selected)
sb.Append("<age>" + item.Text + "</age>"); // actually it's xml-ish
sqlComm.CommandText = #"SELECT * from TableA WHERE Age IN (
SELECT Tab.col.value('.', 'int') as Age from #Ages.nodes('/age') as Tab(col))";
sqlComm.Parameters.Add("#Ages", SqlDbType.NVarChar);
sqlComm.Parameters["#Ages"].Value = sb.ToString();
Using OPENXML method:
using System.Xml.Linq;
...
XElement xml = new XElement("Ages");
foreach (ListItem item in ddlAge.Items)
if (item.Selected)
xml.Add(new XElement("age", item.Text);
sqlComm.CommandText = #"DECLARE #idoc int;
EXEC sp_xml_preparedocument #idoc OUTPUT, #Ages;
SELECT * from TableA WHERE Age IN (
SELECT Age from OPENXML(#idoc, '/Ages/age') with (Age int 'text()')
EXEC sp_xml_removedocument #idoc";
sqlComm.Parameters.Add("#Ages", SqlDbType.Xml);
sqlComm.Parameters["#Ages"].Value = xml.ToString();
That's a bit more on the SQL side and you need a proper XML (with root).
Using OPENJSON method (SQL Server 2016+):
using Newtonsoft.Json;
...
List<string> ages = new List<string>();
foreach (ListItem item in ddlAge.Items)
if (item.Selected)
ages.Add(item.Text);
sqlComm.CommandText = #"SELECT * from TableA WHERE Age IN (
select value from OPENJSON(#Ages))";
sqlComm.Parameters.Add("#Ages", SqlDbType.NVarChar);
sqlComm.Parameters["#Ages"].Value = JsonConvert.SerializeObject(ages);
Note that for the last method you also need to have Compatibility Level at 130+.
Just changing DbType might be enough:
string sqlCommand = "SELECT * from TableA WHERE Age IN (#Age)";
SqlConnection sqlCon = new SqlConnection(connectString);
SqlCommand sqlComm = new SqlCommand
{
Connection = sqlCon,
CommandType = CommandType.Text,
CommandText = sqlCommand,
CommandTimeout = 300
};
var itens = string.Join(',', ddlAge.Items);
sqlComm.Parameters.Add(
new SqlParameter("#Age", itens)
{
DbType = DbType.String
});
Use .AddWithValue(), So:
sqlComm.Parameters.AddWithValue("#Age", sb.ToString().TrimEnd(','));
Alternatively, you could use this:
sqlComm.Parameters.Add(
new SqlParameter("#Age", sb.ToString().TrimEnd(',')) { SqlDbType = SqlDbType. NVarChar }
);
Your total code sample will look at follows then:
string sqlCommand = "SELECT * from TableA WHERE Age IN (#Age)";
SqlConnection sqlCon = new SqlConnection(connectString);
SqlCommand sqlComm = new SqlCommand();
sqlComm.Connection = sqlCon;
sqlComm.CommandType = System.Data.CommandType.Text;
sqlComm.CommandText = sqlCommand;
sqlComm.CommandTimeout = 300;
StringBuilder sb = new StringBuilder();
foreach (ListItem item in ddlAge.Items)
{
if (item.Selected)
{
sb.Append(item.Text + ",");
}
}
sqlComm.Parameters.AddWithValue("#Age", sb.ToString().TrimEnd(','));
// OR
// sqlComm.Parameters.Add(new SqlParameter("#Age", sb.ToString().TrimEnd(',')) { SqlDbType = SqlDbType. NVarChar });
Overview: Use the DbType to set the parameter type.
var parameter = new SqlParameter();
parameter.ParameterName = "#UserID";
parameter.DbType = DbType.Int32;
parameter.Value = userID.ToString();
var command = conn.CreateCommand()
command.Parameters.Add(parameter);
var reader = await command.ExecuteReaderAsync()
try it like this
StringBuilder sb = new StringBuilder();
foreach (ListItem item in ddlAge.Items)
{
if (item.Selected)
{
string sqlCommand = "SELECT * from TableA WHERE Age IN (#Age)";
SqlConnection sqlCon = new SqlConnection(connectString);
SqlCommand sqlComm = new SqlCommand();
sqlComm.Connection = sqlCon;
sqlComm.CommandType = System.Data.CommandType.Text;
sqlComm.CommandText = sqlCommand;
sqlComm.CommandTimeout = 300;
sqlComm.Parameters.Add("#Age", SqlDbType.NVarChar);
sb.Append(item.Text + ",");
sqlComm.Parameters["#Age"].Value = sb.ToString().TrimEnd(',');
}
}
I want to reuse a parameterized query in a loop.
(This query is a simple example, I don't think I could make the loop inside sql and just return the needed rows)
Instead of
private String sql = "SELECT v FROM t WHERE VAL_1 = #param_1";
for (int n=1;n<10;n++)
{
MySqlCommand m = new MySqlCommand(sql);
m.Parameters.AddWithValue("#param_1", n);
res = Convert.ToInt32(m.ExecuteScalar());
( ... )
}
I'd like to move the setup of the query outside the loop; something like
private String sql = "SELECT v FROM t WHERE VAL_1 = #param_1";
MySqlCommand m = new MySqlCommand(sql);
m.Parameters.Add("#param_1"); // does not exist
for (int n=1;n<10;n++)
{
m.Parameters.Set("#param_1", n); // does not exist
res = Convert.ToInt32(m.ExecuteScalar());
( ... )
}
So the server does not have to parse the same sql for each ilteration in loop.
Is that possible?
You can add a parameter with
m.Parameters.Add("#param_1", MySqlDbType.Int32);
and later in the loop assign a value with
m.Parameters["#param_1"].Value = n;
If you just need to run query for list of parms without do diffrent things on each result, You can create a string with a loop like that:
String where_str= VAL_1 = #param_1" OR VAL_1 = #param_2" OR VAL_1 = #param_3"...
String sql = "SELECT v FROM t WHERE " + where_str;
and then exec the query it will give the same result.
If you need to saparate results so you can make it with prepaerd statement. Also, I recommend you to read about stored procedure it may be the best soultion for you in some cases.
example for prepaerd statement: (more info in the link)
private static void SqlCommandPrepareEx(string connectionString)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = new SqlCommand(null, connection);
// Create and prepare an SQL statement.
command.CommandText =
"INSERT INTO Region (RegionID, RegionDescription) " +
"VALUES (#id, #desc)";
SqlParameter idParam = new SqlParameter("#id", SqlDbType.Int, 0);
SqlParameter descParam =
new SqlParameter("#desc", SqlDbType.Text, 100);
idParam.Value = 20;
descParam.Value = "First Region";
command.Parameters.Add(idParam);
command.Parameters.Add(descParam);
// Call Prepare after setting the Commandtext and Parameters.
command.Prepare();
command.ExecuteNonQuery();
// Change parameter values and call ExecuteNonQuery.
command.Parameters[0].Value = 21;
command.Parameters[1].Value = "Second Region";
command.ExecuteNonQuery();
}
}
Yes, this should be possible! Have a look for SQL Prepared Statements!
You can just use:
cmd = new MySqlCommand("SELECT * FROM yourTable WHERE condition=#val1", MySqlConn.conn);
In the loop add the parameters and prepare the command
cmd.Parameters.AddWithValue("#val1", value);
cmd.Prepare();
after the loop execute your query with
cmd.ExecuteNonQuery();
Yep, you can do all of those things but unless that's just an example you'd want to use IN with all the values or a join to a bulk loaded temp table if there are a large number of them. The reason is that each round trip to the DB has a significant overhead that you can reduce from n to 1 with either of those techniques.
Environment:
C#
Visual Studio 2012
.NET Framework 3.5
Hi
Could I parameterize where clause in SQL Server?
In my scenario, once a WHERE clause String is input, application will concatenate it to other part of query and execute in SQL Server then return the result.
For example,
User inputs "[CookingTime] < 30 and [Cost] < 20"
Application creates query "select [RecipeID] from [Recipes] where [CookingTime] < 30 and [Cost] < 20" and executes in SQL Server.
Application returns result to user.
For security reason, I would like to make whole WHERE CLAUSE as parameter.
But I have no idea how to achieve.
Thanks in advance.
This is how it can be done
string commandText = "UPDATE Sales.Store SET Demographics = #demographics "
+ "WHERE CustomerID = #ID;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand(commandText, connection);
command.Parameters.Add("#ID", SqlDbType.Int);
command.Parameters["#ID"].Value = customerID;
// Use AddWithValue to assign Demographics.
// SQL Server will implicitly convert strings into XML.
command.Parameters.AddWithValue("#demographics", demoXml);
try
{
connection.Open();
Int32 rowsAffected = command.ExecuteNonQuery();
Console.WriteLine("RowsAffected: {0}", rowsAffected);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
The whole WHERE clause as parameter will be a victim of sql injection in any way. To prevent this you'd better to:
Setup proper permissions. So even in case of sql injected user can't access anything not granted. In this case sample of #Dhaval is better, because dymanic sql generation incapsulated in stored procedure requires less permissions to execute.
Check the statement for sql injection. The simplest way is to check for semicolons in order to avoid another statements in the batch. More complex and more precise way is to use t-sql DOM parser. For example:
using Microsoft.SqlServer.TransactSql.ScriptDom;
TSql110Parser parser = new TSql110Parser(true);
IList<ParseError> errors = null;
var condition = "a > 100; delete from [Recipes]";
var script = parser.Parse(new StringReader("select [RecipeID] from [Recipes] where " + condition), out errors) as TSqlScript;
if (errors.Count > 0)
{
throw new Exception(errors[0].Message);
}
foreach (var batch in script.Batches)
{
if (batch.Statements.Count == 1)
{
var select = batch.Statements[0] as SelectStatement;
if (select != null)
{
QuerySpecification query = select.QueryExpression as QuerySpecification;
if (query.WhereClause is BooleanBinaryExpression)
{
...
}
}
else
{
throw new Exception("Select statement only allowed");
}
}
else
{
throw new Exception("More than one statement detected");
}
}
You can create a dynamic query in sql server and pass the parameter from C#
Something like this
Create Procedure usp_Test
#WhereCond Varchar(max)
AS
Bgein
Set NoCount ON
Declare #SQLQuery AS Varchar(max)
Set #SQLQuery = 'Select * From tblEmployees where ' + #WhereCond
Execute sp_Executesql #SQLQuery
End
C# Code to execute the procedure
DataSet ds = new DataSet();
using(SqlConnection conn = new SqlConnection("ConnectionString"))
{
SqlCommand sqlComm = new SqlCommand("usp_Test", conn);
sqlComm.Parameters.AddWithValue("#WhereCond", WhereCond);
sqlComm.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = sqlComm;
da.Fill(ds);
}
I guess the original question wanted to find out how to make it dynamically from user's input and then use proper sql parameter to do the query.
For the usage of sql parameter, normally what I do is to use a generic helper method, a quick example (not tested):
public static class SqlHelpers
{
public static IEnumerable<T> ExecuteAdhocQuery<T>(SqlConnection con, string sql, CommandType cmdType, Func<SqlDataReader, T> converter, params SqlParameter[] args)
{
try
{
using (SqlCommand cmd = new SqlCommand(sql, con) { CommandType = cmdType })
{
cmd.Parameters.AddRange(args);
if (con.State != ConnectionState.Open) { con.Open(); }
var ret = new List<T>();
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
ret.Add(converter.Invoke(rdr));
}
}
return ret;
}
}
catch (Exception e)
{
// log error?
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
throw e; // handle exception...
}
}
public void Test()
{
using (SqlConnection con = new SqlConnection("connection string here"))
{
var data = ExecuteAdhocQuery(con,
"SELECT ID, Name FROM tblMyTable WHERE ID = #Id and Status = #Status;",
CommandType.Text, (x) => new { Id = x.GetInt32(0), Name = x.GetString(1) },
new SqlParameter("#Id", SqlDbType.Int) { Value = 1 },
new SqlParameter("#Status", SqlDbType.Bit) { Value = true });
Console.WriteLine(data.Count());
}
}
}
of course, this is only Reading, for Insert/Update, similar methods could be created too.
But the complicated part is how to make it dynamic with unknown number of conditions and the relationship between them. So a quick suggestion is use a delegated method or class to do the work. sample (not tested):
public static Dictionary<string, SqlParameter> GetParamsFromInputString(string inputString)
{
var output = new Dictionary<string, SqlParameter>();
// use Regex to translate the input string (something like "[CookingTime] < 30 and [Cost] < 20" ) into a key value pair
// and then build sql parameter and return out
// The key will be the database field while the corresponding value is the sql param with value
return output;
}
public void TestWithInput(string condition)
{
var parameters = GetParamsFromInputString(condition);
// first build up the sql query:
var sql = "SELECT Id, Name from tblMyTable WHERE " + parameters.Select(m => string.Format("{0}={1}", m.Key, m.Value.ParameterName)).Aggregate((m,n) => m + " AND " + n);
using (SqlConnection con = new SqlConnection("connection string here"))
{
var data = ExecuteAdhocQuery(con,
sql,
CommandType.Text,
(x) => new { Id = x.GetInt32(0), Name = x.GetString(1) },
parameters.Select(m => m.Value).ToArray());
}
}
for the static function GetParamsFromInputString, it's just a sample. actually it could be very complicated depending on your needs.
for example, you might want to include the operator (whether it's >, < or <>,...).
and you might also want to include the conjunctions between the conditions, whether it's AND or OR.
Build delegated classes to do the job if it's very complicated.
I am trying to pass array parameter to SQL commnd in C# like below, but it does not work. Does anyone meet it before?
string sqlCommand = "SELECT * from TableA WHERE Age IN (#Age)";
SqlConnection sqlCon = new SqlConnection(connectString);
SqlCommand sqlComm = new SqlCommand();
sqlComm.Connection = sqlCon;
sqlComm.CommandType = System.Data.CommandType.Text;
sqlComm.CommandText = sqlCommand;
sqlComm.CommandTimeout = 300;
sqlComm.Parameters.Add("#Age", SqlDbType.NVarChar);
StringBuilder sb = new StringBuilder();
foreach (ListItem item in ddlAge.Items)
{
if (item.Selected)
{
sb.Append(item.Text + ",");
}
}
sqlComm.Parameters["#Age"].Value = sb.ToString().TrimEnd(',');
You will need to add the values in the array one at a time.
var parameters = new string[items.Length];
var cmd = new SqlCommand();
for (int i = 0; i < items.Length; i++)
{
parameters[i] = string.Format("#Age{0}", i);
cmd.Parameters.AddWithValue(parameters[i], items[i]);
}
cmd.CommandText = string.Format("SELECT * from TableA WHERE Age IN ({0})", string.Join(", ", parameters));
cmd.Connection = new SqlConnection(connStr);
UPDATE: Here is an extended and reusable solution that uses Adam's answer along with his suggested edit. I improved it a bit and made it an extension method to make it even easier to call.
public static class SqlCommandExt
{
/// <summary>
/// This will add an array of parameters to a SqlCommand. This is used for an IN statement.
/// Use the returned value for the IN part of your SQL call. (i.e. SELECT * FROM table WHERE field IN ({paramNameRoot}))
/// </summary>
/// <param name="cmd">The SqlCommand object to add parameters to.</param>
/// <param name="paramNameRoot">What the parameter should be named followed by a unique value for each value. This value surrounded by {} in the CommandText will be replaced.</param>
/// <param name="values">The array of strings that need to be added as parameters.</param>
/// <param name="dbType">One of the System.Data.SqlDbType values. If null, determines type based on T.</param>
/// <param name="size">The maximum size, in bytes, of the data within the column. The default value is inferred from the parameter value.</param>
public static SqlParameter[] AddArrayParameters<T>(this SqlCommand cmd, string paramNameRoot, IEnumerable<T> values, SqlDbType? dbType = null, int? size = null)
{
/* An array cannot be simply added as a parameter to a SqlCommand so we need to loop through things and add it manually.
* Each item in the array will end up being it's own SqlParameter so the return value for this must be used as part of the
* IN statement in the CommandText.
*/
var parameters = new List<SqlParameter>();
var parameterNames = new List<string>();
var paramNbr = 1;
foreach (var value in values)
{
var paramName = string.Format("#{0}{1}", paramNameRoot, paramNbr++);
parameterNames.Add(paramName);
SqlParameter p = new SqlParameter(paramName, value);
if (dbType.HasValue)
p.SqlDbType = dbType.Value;
if (size.HasValue)
p.Size = size.Value;
cmd.Parameters.Add(p);
parameters.Add(p);
}
cmd.CommandText = cmd.CommandText.Replace("{" + paramNameRoot + "}", string.Join(",", parameterNames));
return parameters.ToArray();
}
}
It is called like this...
var cmd = new SqlCommand("SELECT * FROM TableA WHERE Age IN ({Age})");
cmd.AddArrayParameters("Age", new int[] { 1, 2, 3 });
Notice the "{Age}" in the sql statement is the same as the parameter name we are sending to AddArrayParameters. AddArrayParameters will replace the value with the correct parameters.
I wanted to expand on the answer that Brian contributed to make this easily usable in other places.
/// <summary>
/// This will add an array of parameters to a SqlCommand. This is used for an IN statement.
/// Use the returned value for the IN part of your SQL call. (i.e. SELECT * FROM table WHERE field IN (returnValue))
/// </summary>
/// <param name="sqlCommand">The SqlCommand object to add parameters to.</param>
/// <param name="array">The array of strings that need to be added as parameters.</param>
/// <param name="paramName">What the parameter should be named.</param>
protected string AddArrayParameters(SqlCommand sqlCommand, string[] array, string paramName)
{
/* An array cannot be simply added as a parameter to a SqlCommand so we need to loop through things and add it manually.
* Each item in the array will end up being it's own SqlParameter so the return value for this must be used as part of the
* IN statement in the CommandText.
*/
var parameters = new string[array.Length];
for (int i = 0; i < array.Length; i++)
{
parameters[i] = string.Format("#{0}{1}", paramName, i);
sqlCommand.Parameters.AddWithValue(parameters[i], array[i]);
}
return string.Join(", ", parameters);
}
You can use this new function as follows:
SqlCommand cmd = new SqlCommand();
string ageParameters = AddArrayParameters(cmd, agesArray, "Age");
sql = string.Format("SELECT * FROM TableA WHERE Age IN ({0})", ageParameters);
cmd.CommandText = sql;
Edit:
Here is a generic variation that works with an array of values of any type and is usable as an extension method:
public static class Extensions
{
public static void AddArrayParameters<T>(this SqlCommand cmd, string name, IEnumerable<T> values)
{
name = name.StartsWith("#") ? name : "#" + name;
var names = string.Join(", ", values.Select((value, i) => {
var paramName = name + i;
cmd.Parameters.AddWithValue(paramName, value);
return paramName;
}));
cmd.CommandText = cmd.CommandText.Replace(name, names);
}
}
You can then use this extension method as follows:
var ageList = new List<int> { 1, 3, 5, 7, 9, 11 };
var cmd = new SqlCommand();
cmd.CommandText = "SELECT * FROM MyTable WHERE Age IN (#Age)";
cmd.AddArrayParameters("Age", ageList);
Make sure you set the CommandText before calling AddArrayParameters.
Also make sure your parameter name won't partially match anything else in your statement (i.e. #AgeOfChild)
If you can use a tool like "dapper", this can be simply:
int[] ages = { 20, 21, 22 }; // could be any common list-like type
var rows = connection.Query<YourType>("SELECT * from TableA WHERE Age IN #ages",
new { ages }).ToList();
Dapper will handle unwrapping this to individual parameters for you.
If you are using MS SQL Server 2008 and above you can use table-valued parameters like described here
http://www.sommarskog.se/arrays-in-sql-2008.html.
1. Create a table type for each parameter type you will be using
The following command creates a table type for integers:
create type int32_id_list as table (id int not null primary key)
2. Implement helper methods
public static SqlCommand AddParameter<T>(this SqlCommand command, string name, IEnumerable<T> ids)
{
var parameter = command.CreateParameter();
parameter.ParameterName = name;
parameter.TypeName = typeof(T).Name.ToLowerInvariant() + "_id_list";
parameter.SqlDbType = SqlDbType.Structured;
parameter.Direction = ParameterDirection.Input;
parameter.Value = CreateIdList(ids);
command.Parameters.Add(parameter);
return command;
}
private static DataTable CreateIdList<T>(IEnumerable<T> ids)
{
var table = new DataTable();
table.Columns.Add("id", typeof (T));
foreach (var id in ids)
{
table.Rows.Add(id);
}
return table;
}
3. Use it like this
cmd.CommandText = "select * from TableA where Age in (select id from #age)";
cmd.AddParameter("#age", new [] {1,2,3,4,5});
Since there is a method on
SqlCommand.Parameters.AddWithValue(parameterName, value)
it might be more convenient to create a method accepting a parameter (name) to replace and a list of values. It is not on the Parameters level (like AddWithValue) but on command itself so it's better to call it AddParametersWithValues and not just AddWithValues:
query:
SELECT * from TableA WHERE Age IN (#age)
usage:
sqlCommand.AddParametersWithValues("#age", 1, 2, 3);
the extension method:
public static class SqlCommandExtensions
{
public static void AddParametersWithValues<T>(this SqlCommand cmd, string parameterName, params T[] values)
{
var parameterNames = new List<string>();
for(int i = 0; i < values.Count(); i++)
{
var paramName = #"#param" + i;
cmd.Parameters.AddWithValue(paramName, values.ElementAt(i));
parameterNames.Add(paramName);
}
cmd.CommandText = cmd.CommandText.Replace(parameterName, string.Join(",", parameterNames));
}
}
I want to propose another way, how to solve limitation with IN operator.
For example we have following query
select *
from Users U
WHERE U.ID in (#ids)
We want to pass several IDs to filter users. Unfortunately it is not possible to do with C# in easy way. But I have fount workaround for this by using "string_split" function. We need to rewrite a bit our query to following.
declare #ids nvarchar(max) = '1,2,3'
SELECT *
FROM Users as U
CROSS APPLY string_split(#ids, ',') as UIDS
WHERE U.ID = UIDS.value
Now we can easily pass one parameter enumeration of values separated by comma.
Passing an array of items as a collapsed parameter to the WHERE..IN clause will fail since query will take form of WHERE Age IN ("11, 13, 14, 16").
But you can pass your parameter as an array serialized to XML or JSON:
Using nodes() method:
StringBuilder sb = new StringBuilder();
foreach (ListItem item in ddlAge.Items)
if (item.Selected)
sb.Append("<age>" + item.Text + "</age>"); // actually it's xml-ish
sqlComm.CommandText = #"SELECT * from TableA WHERE Age IN (
SELECT Tab.col.value('.', 'int') as Age from #Ages.nodes('/age') as Tab(col))";
sqlComm.Parameters.Add("#Ages", SqlDbType.NVarChar);
sqlComm.Parameters["#Ages"].Value = sb.ToString();
Using OPENXML method:
using System.Xml.Linq;
...
XElement xml = new XElement("Ages");
foreach (ListItem item in ddlAge.Items)
if (item.Selected)
xml.Add(new XElement("age", item.Text);
sqlComm.CommandText = #"DECLARE #idoc int;
EXEC sp_xml_preparedocument #idoc OUTPUT, #Ages;
SELECT * from TableA WHERE Age IN (
SELECT Age from OPENXML(#idoc, '/Ages/age') with (Age int 'text()')
EXEC sp_xml_removedocument #idoc";
sqlComm.Parameters.Add("#Ages", SqlDbType.Xml);
sqlComm.Parameters["#Ages"].Value = xml.ToString();
That's a bit more on the SQL side and you need a proper XML (with root).
Using OPENJSON method (SQL Server 2016+):
using Newtonsoft.Json;
...
List<string> ages = new List<string>();
foreach (ListItem item in ddlAge.Items)
if (item.Selected)
ages.Add(item.Text);
sqlComm.CommandText = #"SELECT * from TableA WHERE Age IN (
select value from OPENJSON(#Ages))";
sqlComm.Parameters.Add("#Ages", SqlDbType.NVarChar);
sqlComm.Parameters["#Ages"].Value = JsonConvert.SerializeObject(ages);
Note that for the last method you also need to have Compatibility Level at 130+.
Just changing DbType might be enough:
string sqlCommand = "SELECT * from TableA WHERE Age IN (#Age)";
SqlConnection sqlCon = new SqlConnection(connectString);
SqlCommand sqlComm = new SqlCommand
{
Connection = sqlCon,
CommandType = CommandType.Text,
CommandText = sqlCommand,
CommandTimeout = 300
};
var itens = string.Join(',', ddlAge.Items);
sqlComm.Parameters.Add(
new SqlParameter("#Age", itens)
{
DbType = DbType.String
});
Use .AddWithValue(), So:
sqlComm.Parameters.AddWithValue("#Age", sb.ToString().TrimEnd(','));
Alternatively, you could use this:
sqlComm.Parameters.Add(
new SqlParameter("#Age", sb.ToString().TrimEnd(',')) { SqlDbType = SqlDbType. NVarChar }
);
Your total code sample will look at follows then:
string sqlCommand = "SELECT * from TableA WHERE Age IN (#Age)";
SqlConnection sqlCon = new SqlConnection(connectString);
SqlCommand sqlComm = new SqlCommand();
sqlComm.Connection = sqlCon;
sqlComm.CommandType = System.Data.CommandType.Text;
sqlComm.CommandText = sqlCommand;
sqlComm.CommandTimeout = 300;
StringBuilder sb = new StringBuilder();
foreach (ListItem item in ddlAge.Items)
{
if (item.Selected)
{
sb.Append(item.Text + ",");
}
}
sqlComm.Parameters.AddWithValue("#Age", sb.ToString().TrimEnd(','));
// OR
// sqlComm.Parameters.Add(new SqlParameter("#Age", sb.ToString().TrimEnd(',')) { SqlDbType = SqlDbType. NVarChar });
Overview: Use the DbType to set the parameter type.
var parameter = new SqlParameter();
parameter.ParameterName = "#UserID";
parameter.DbType = DbType.Int32;
parameter.Value = userID.ToString();
var command = conn.CreateCommand()
command.Parameters.Add(parameter);
var reader = await command.ExecuteReaderAsync()
try it like this
StringBuilder sb = new StringBuilder();
foreach (ListItem item in ddlAge.Items)
{
if (item.Selected)
{
string sqlCommand = "SELECT * from TableA WHERE Age IN (#Age)";
SqlConnection sqlCon = new SqlConnection(connectString);
SqlCommand sqlComm = new SqlCommand();
sqlComm.Connection = sqlCon;
sqlComm.CommandType = System.Data.CommandType.Text;
sqlComm.CommandText = sqlCommand;
sqlComm.CommandTimeout = 300;
sqlComm.Parameters.Add("#Age", SqlDbType.NVarChar);
sb.Append(item.Text + ",");
sqlComm.Parameters["#Age"].Value = sb.ToString().TrimEnd(',');
}
}