I have a way to write a SqlCommand which includes a dynamic list of parameters.
My challenge is passing each of the new SqlParameter (#Param0, value0), new sqlParameter (#Param1, value1)... could be another 50 SQL parameters. It can be passed as a hard-coded string but passing the sb.ToString() understandably won't work (because of the commas - they are new arguments).
How do I write a loop or similar to pass the correct number of new arguments?
My attempt so far is below:
public ViewResult Index(int? ID)
{
using (var context = new Projects201819Context())
if (ID == null)
{
var sqlCommand = new SqlCommand();
// Array of item numbers - will change and can be longer/shorter, as required.
var SQL0 = "SELECT * FROM [database] WHERE material_id IN ({0})";
var idList = new List<int> { 11, 53, 125};
int[] idListArray = idList.ToArray();
var idParameterList = new List<string>();
var index = 0;
int IL = idList.Count;
// Create a SqlParameter for each element in the array called "#idParam0", "#idParam1"... and add to list idParameterList
foreach (var id in idList)
{
var paramName = "#idParam" + index;
sqlCommand.Parameters.AddWithValue(paramName, id);
idParameterList.Add(paramName);
index++;
}
// Finalise SQL String for datainput - DONE AND WORKS
sqlCommand.CommandText = String.Format(SQL0, string.Join(",", idParameterList));
var newSPList = new List<string>();
var m = 0;
foreach (var id in idList)
{
var SPName = " new SqlParameter(" + "\"" + "#idParam" + m + "\"" + "," + idListArray[m] + ")";
newSPList.Add(SPName);
m++;
}
string HELLO = string.Join(",", newSPList);
string MM = "\"" + sqlCommand.CommandText + "\"" + "," + HELLO;
var datainput = context.materials.SqlQuery(MM);
var data = datainput.ToList();
return View(data);
}
}
where there is an id is fine and not given (the else part of if (id == null)).
The critical bit is the SPName - this successfully adds items to the newSPList list and the string.join returns the exact string I need (HELLO) but I can't then pass this long string as separate arguments - makes complete sense - I just don't know how to work around it!
Thank you for any support!
Let SQL Server do all dirty work. Something like this.
var SQL0 = "SELECT * FROM [database] WHERE material_id IN (select value from string_split('{0}',','))";
var idList = new List<int> { 11, 53, 125};
int[] idListArray = idList.ToArray();
sqlCommand.CommandText = String.Format(SQL0, string.Join(",", idListArray));
// now execute the command
EDIT
More secure and performat way.
var SQL0 = "SELECT * FROM [database] WHERE material_id IN (select value from string_split(#ids,','))";
var idList = new List<int> { 11, 53, 125};
int[] idListArray = idList.ToArray();
sqlCommand.CommandText = SQL0;
sqlCommand.Parameters.Add("#ids", SqlDbTypes.VarChar, -1).Value = string.Join(",", idListArray);
// now execute the command
You cannot pass an array of parameters in that way. SqlQuery from EF6 has an overload that accepts, as second parameter, an array of SqlParameter.
All you have to do is:
SqlParameter[] pms = sqlCommand.Parameters.Cast<SqlParameter>().ToArray();
var datainput = context.materials.SqlQuery(sqlCommand.CommandText, pms);
Of course this means also that a lot of your current code is unnecessary and you can scrap it away
For example, you could write this without an SqlCommand object used just to store parameters and the command text.
var SQL0 = "SELECT * FROM [database] WHERE material_id IN ({0})";
var idList = new List<int> { 11, 53, 125 };
var idParameterList = new List<string>();
var pms = new List<SqlParameter>();
int count = 1;
foreach (var id in idList)
{
var paramName = "#idParam" + count++;
SqlParameter p = new SqlParameter(paramName, SqlDbType.Int);
p.Value = id;
pms.Add(p);
idParameterList.Add(paramName);
}
string cmdText = String.Format(SQL0, string.Join(",", idParameterList));
var datainput = context.materials.SqlQuery(cmdText, pms);
Related
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 try to delete some rows from a table in an access database file via C#.
This attempt fails with no error which leads me to the conclusion that I have a valid query with incorrect data.
I tried to see if I can query the data with a select statement from my code and I can narrow the problem down to the parameters.
The statement should look as follows
SELECT * FROM tbIndex where pguid in ('4a651816-e15b-4c6a-85c4-74033ca6c423', '0add7bff-a22f-4238-9c7f-e1ff4ed3c7e2', '742fae8b-2692-4a6f-802c-848fad570696', '5e6b65de-2403-4800-a47d-e57c7bd8e0a6')
I tried two different ways*(dbCmd2 and dbCmd3)* from which the first*(dbCmd2)* works but is, due to injection problems, not my prefered solution.
using (OleDbCommand dbCmd2 = new OleDbCommand { Connection = m_Connection })
{
dbCmd2.CommandText = "SELECT * FROM tbIndex where pguid in ("+pguid+")";
using (DbDataReader reader = dbCmd2.ExecuteReader())
{
List<object[]> readValuesFromIndex = new List<object[]>();
while (reader.Read())
{
//Point reached
object[] arr = new object[reader.VisibleFieldCount];
reader.GetValues(arr);
//...
}
reader.Close();
}
using (OleDbCommand dbCmd3 = new OleDbCommand { Connection = m_Connection })
{
dbCmd3.CommandText = "SELECT * FROM tbIndex where pguid in (#pguid)";
dbCmd3.Parameters.Add("#pguid", OleDbType.VarChar).Value = pguid;
using (DbDataReader reader = dbCmd3.ExecuteReader())
{
List<object[]> readValuesFromIndex = new List<object[]>();
while (reader.Read())
{
//Point not reached
object[] arr = new object[reader.VisibleFieldCount];
reader.GetValues(arr);
//...
}
reader.Close();
}
}
Note that pguid is set to "'4a651816-e15b-4c6a-85c4-74033ca6c423', '0add7bff-a22f-4238-9c7f-e1ff4ed3c7e2', '742fae8b-2692-4a6f-802c-848fad570696', '5e6b65de-2403-4800-a47d-e57c7bd8e0a6'".
I always thought that the second option would simply replace the parameter in a safe manner but this is obviously not the case.
My question is:
Why doesn't the second option return any values?
A parameter always is a single value.
An in clause requires multiple values, separated by comma's.
You can do something like the following to pass them like separate parameters:
string[] guids = pguid.Split(',');
string sqlin = "";
int paramno = -1;
foreach (var guid in guids)
{
parametercount ++;
sqlin = sqlin + "#Param" + (string)parametercount; + ","
}
dbCmd3.CommandText = "SELECT * FROM tbIndex where pguid in (" + sqlin.Substring(0, sqlin.Length-1) + ")";
for(int i = 0; i <= parametercount; i++){
dbCmd3.Parameters.Add("#Param" + (string)i, OleDbType.VarChar).Value = guids[i].Replace("'", "");
}
This question already has answers here:
parameterized queries vs. SQL injection
(4 answers)
Closed 6 years ago.
string[] theParms = ['parm1', 'parm2'];
string theQuery = "SELECT something, somethingAgain " +
"FROM aDBTable " +
"WHERE something = '{?}'" +
"AND something <> '{?}'";
I am needing to replace the {?}'s with the defined parms in theParms.
Is there some type of loop in C# that I can use in order to loop through the string and replace each found {?} with the corresponding parm value?
Something like this:
First loop:
SELECT something, somethingAgain
FROM aDBTable
WHERE something = 'parm1' AND something <> '{?}'
Second loop:
SELECT something, somethingAgain
FROM aDBTable
WHERE something = 'parm1' AND something <> 'parm2'
Is there some type of REGEX or common framework function that can do the above?
sql injection check
bool injectionCheckin = new injectionCheck().inCheck(theFinalQuery);
public class injectionCheck
{
public bool inCheck(string queryString)
{
var badWords = new[] {
"EXEC", "EXECUTE", ";", "-", "*", "--", "#",
"UNION", "DROP","DELETE", "UPDATE", "INSERT",
"MASTER", "TABLE", "XP_CMDSHELL", "CREATE",
"XP_FIXEDDRIVES", "SYSCOLUMNS", "SYSOBJECTS",
"BC_HOME_ADDRESS1", "BC_HOME_ADDRESS2", "BC_HOME_CITY", "BC_HOME_COUNTY", "BC_HOME_POSTAL", "BC_MAIL_ADDRESS1",
"BC_MAIL_ADDRESS2", "BC_MAIL_CITY", "BC_MAIL_COUNTY", "BC_MAIL_POSTAL", "BC_MAIL_STATE", "FLSA_STATUS", "GRADE",
"GRADE_ENTRY_DT", "HIGHEST_EDUC_LVL", "LAST_INCREASE_DT", "BC_SALP_DESCR", "BC_SALP_DESCRSHORT", "SAL_ADMIN_PLAN"
};
string pattern = "(?<!\\w)(" + Regex.Escape(badWords[0]);
foreach (var key in badWords.Skip(1))
{
pattern += "|" + Regex.Escape(key);
}
pattern += ")(?!\\w)";
dynamic _tmpCount = Regex.Matches(queryString, pattern, RegexOptions.IgnoreCase).Count;
if (_tmpCount >= 1)
return true;
else
return false;
}
}
Always create Sql-commands by parameterized queries:
using (SqlConnection conn = new SqlConnection(DatabaseConnectionString))
using (SqlCommand cmd = conn.CreateCommand())
{
var #params = new Dictionary<string, object>{
{ "something", myValue },
{ "somethingDifferent", anotherValue },
};
cmd.CommandText = "SELECT something, somethingAgain " +
"FROM aDBTable " +
"WHERE something = #something'" +
"AND something <> #somethingDifferent'";
foreach (KeyValuePair<string, object> item in values)
{
cmd.Parameters.AddWithValue("#" + item.Key, item.Value);
}
DataTable table = new DataTable();
using (var reader = cmd.ExecuteReader())
{
table.Load(reader);
return table;
}
}
}
This prevents all sort of SqlInjection and you won´t any weird checks as yours with the badlist which is quite messy and does not really prevent you, you can easily bypass the list with some escaping for instance. In particular: why do you want to write your own validation when there allready are ready-to-use methods that do exactly what you want?
Why not just use String.Format?
string[] theParms = new string[] { "parm1", "parm2" };
string theQuery = #"SELECT something, somethingAgain
FROM aDBTable
WHERE something = '{0}'
AND something <> '{1}'";
var res = string.Format(theQuery, theParms);
Result:
SELECT something, somethingAgain
FROM aDBTable
WHERE something = 'parm1'
AND something <> 'parm2'
If you want to do it in either case, you could do it without a loopy as follows.
string theQuery = String.Format( "SELECT something, somethingAgain " +
"FROM aDBTable " +
"WHERE something = '{0}'" +
"AND something <> '{1}'",
theParams[0], theParams[1] );
Okay, to avoid Injection and all that, why don't you do it like this:
string[] theParms = // initialization
string theQuery = // initialization
SqlCommand cmd = new SqlCommand(/* enter connection string */, theQuery)
for(int i = 0; i < theParams.Length; i++)
{
int index = cmd.Text.IndexOf("{?}");
if(index > -1)
{
string pName = string.Format("#p{0}", i);
cmd.Text = cmd.Text.Remove(index, 3).Insert(index, pName);
cmd.Parameters.Add(new SqlParameter() { Name = pName, Value = theParms[i] });
}
}
That should avoid any manual injection checks alltogether... at least if you can't get the query pre-compiled and have to load it at runtime. Otherwise just formulate the SqlCommand's text appropriately and you'll not need a loop or anything. Just a simple initialization:
SqlCommand cmd = new SqlCommand(/* enter connection string */, "SELECT something, somethingAgain FROM aDBTable WHERE something = #p0 AND something <> #p1");
cmd.Parameters.Add(new SqlParameter() { Name = "#p0", Value = theParms[0] });
cmd.Parameters.Add(new SqlParameter() { Name = "#p1", Value = theParms[1] });
You can use IndexOf and substrings to find each instance
for(int i = 0; i < theParms.GetLength(0); i++)
{
string[] tempStrings = new string[]{ theQuery.Substring(0,theQuery.IndexOf("{?}") - 1),
theQuery.Substring(theQuery.IndexOf("{?}"), 3),
theQuery.Substring(theQuery.IndexOf("{?}") + 4) }
tempStrings[1] = tempStrings[1].Replace("{?}", theParms[i]);
theQuery = String.Join("", tempStrings);
}
Though seeing as you check for injection afterwards it is definitely much better to use String.Format
You don't have to handle it by yourself. Instead, ADO.NET allows you define parameters and set their values. See the sample here .MSDN
This is my query
SELECT COUNT(MPRO.OriDes0154) 'QueueCalls' FROM MMProdat.dbo.InstTaWf0154 MPRO WITH(NOLOCK)
WHERE MPRO.Estado0154 = 'QUEUED'
AND F1IdWI02140154<>'VOICEMAIL'
AND F1Camp02930154 = 'Support'
please notice that i have AND F1Camp02930154 = 'Support'
Now I have a list like this:
List<string> compaines = new List<string>();
the values in this list should be in this conidtion AND F1Camp02930154 = 'Support'
for example if the list is empty, i will get an empty result, but if the list has just one value which is Support the query will be
AND F1Camp02930154 = 'Support'
but if the list has two vaules which are Support and Sales then the query will be
AND F1Camp02930154 = 'Support' and `Sales`
how to do that please in c#
where I already have this:
string queryString = "SELECT COUNT(MPRO.OriDes0154) 'QueueCalls' FROM MMProdat.dbo.InstTaWf0154 MPRO WITH(NOLOCK) WHERE MPRO.Estado0154 = 'QUEUED' AND F1IdWI02140154<>'VOICEMAIL'";
Update 1
After # Gordon Linoff comment
I tried this:
List<string> compaines = new List<string>();
string queryString = "SELECT COUNT(MPRO.OriDes0154) 'QueueCalls' FROM MMProdat.dbo.InstTaWf0154 MPRO WITH(NOLOCK) WHERE MPRO.Estado0154 = 'QUEUED' AND F1IdWI02140154<>'VOICEMAIL' AND F1Camp02930154 IN (";
for (int i = 0; i < compaines.Count(); i++) {
queryString += "'" + compaines[i] + "'";
}
queryString += ")";
You can use String.Join() to construct IN () clause, for example :
var companies = new List<string>() { "Support", "Sales" };
string inClause = String.Format(" IN ('{0}')", String.Join("', '", companies));
Console.WriteLine("AND F1Camp02930154" + inClause);
//output :
//AND F1Camp02930154 IN ('Support', 'Sales')
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(',');
}
}