How to convert String into string array in C#? [duplicate] - c#

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(',');
}
}

Related

OleDb Parameters Array Format

Hi in the OleDB documentation (linked below) they have a parameter called OleDbParameter[] parametersin a functions, a loop then iterates over that array to insert the parameters into the SQL.
I can't find in the documentation how you are supposed to format that array?
Docs: https://learn.microsoft.com/en-us/dotnet/api/system.data.oledb.oledbcommand.parameters?view=dotnet-plat-ext-3.1
The code in the Microsoft Example is wrong and not compilable. I have already sent a feedback about it. Here a more correct version with annotations about what to change.
public void CreateMyOleDbCommand(OleDbConnection connection,
string queryString, OleDbParameter[] parameters)
{
OleDbCommand command = new OleDbCommand(queryString, connection);
// This destroys the commandtext set by querystring in the constructor.
// command.CommandText =
// "SELECT CustomerID, CompanyName FROM Customers WHERE Country = ? AND City = ?";
// This cannot work, you can't add an array with Add but only one element
// command.Parameters.Add(parameters);
command.Parameters.AddRange(parameters);
// After AddRange this loop is pointless and creates possible errors with
// too many parameters added to the collection
// for (int j=0; j<parameters.Length; j++)
//{
// command.Parameters.Add(parameters[j]) ;
//}
string message = "";
for (int i = 0; i < command.Parameters.Count; i++)
{
message += command.Parameters[i].ToString() + "\n";
}
Console.WriteLine(message);
}
Now to call this function you have
string cmdText = #"SELECT CustomerID, CompanyName
FROM Customers
WHERE Country = ? AND City = ?";
OleDbParameter[] parameters = new OleDbParameter[]
{
new OleDbParameter{ ParameterName = "?",OleDbType = OleDbType.VarWChar, Value = "UK" },
new OleDbParameter{ ParameterName = "?",OleDbType = OleDbType.VarWChar, Value = "London"},
};
CreateMyOleDbCommand(myConnection, cmdText, parameters);

Access parametrized IN() query

I am trying to run this query:
SELECT * FROM Tabela1 WHERE Pole1 IN (#parameter)
When the window with "choose value" appears, I put: "10, 50" and I receive 0 rows (should be 2).
When I put only "10", or only "50" it works and returns 1 row for each query.
I am using Access 2013 - what am I doing wrong?
using (DbConnection connection = new T())
{
connection.ConnectionString = query.DatabaseConnection.ConnectionString;
using (DbCommand command = connection.CreateCommand())
{
command.CommandText = query.QuerySql.Sql;
command.CommandType = CommandType.Text;
command.CommandTimeout = query.QuerySql.CommandTimeout;
if (query.QuerySql.Parameters != null)
{
foreach (var parameter in query.QuerySql.Parameters)
{
var commandParameter = command.CreateParameter();
commandParameter.ParameterName = $"#{parameter.Name}";
commandParameter.Value = parameter.Value;
command.Parameters.Add(commandParameter);
}
}
I create the query like that:
QuerySql sql = new QuerySql("SELECT * FROM Tabela1 WHERE Pole1 IN(#parameter)", new List<ISqlParameter>()
{
new SqlMultiNumberParameter("parameter", new List<string>() { "10", "50" }, "Test parameter")
});
the parameter.Value returns string like that: "10, 50"
Best regards
Michael
You cannot use a single parameter to express a list of values to be passed to an IN clause. There are some ORM (like Dapper for example) that allows you to pass a list of values and build for you the correct IN clause.
If you want to do the same thing you need something like this method
public OleDbCommand GetPoles(List<int> polesID)
{
// Base text of the query
string cmdText = #"SELECT * FROM TABLE1 WHERE Pole1 IN(";
// where we store the 'name' of the parameters. (OleDb doesn't care)
List<string> inClause = new List<string>();
// where we store the parameters and their values
List<OleDbParameter> parameters = new List<OleDbParameter>();
foreach(int id in polesID)
{
// Add a placeholder for the parameter
inClause.Add("?");
// Build the parameter and store it away
OleDbParameter p = new OleDbParameter("p" + id.ToString(), OleDbType.Integer);
p.Value = id;
parameters.Add(p);
}
OleDbCommand cmd = new OleDbCommand();
// Build the command text: IN(?,?,?). A ? placeholder for each parameter
cmd.CommandText = cmdText + string.Join(",", inClause.ToArray()) + ")";
// pass all the parameters to the command and return it
cmd.Parameters.AddRange(parameters.ToArray());
return cmd;
}
Now you just need to set the connection and you can execute the command

Add string array to SQL query

I have a string array which consists of identifiers. I want to get some values from SQL using these identifiers . Is there a way of adding them with a string value to SqlCommand parameters?
I want to create a query like:
select CaseList from MasterReportData where Id = 1 OR Id = 2 OR Id = 3
This is my C# code:
public static List<string> GetCaseList(string[] masterIdList)
{
try
{
string query = " select CaseList from MasterReportData where #masterId";
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(query, conn);
cmd.Parameters.AddWithValue("masterId", ***);
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
list.Add(reader[0].ToString());
}
}
conn.Close();
}
catch (Exception e)
{
var err= 0;
}
return list;
}
There are many different ways you can go about doing this but I prefer to create a temp table of possible values. That way you can do something like
select CaseList from MasterReportData where Id IN(select Id from tempTable)
The best approach (with sql optimization) would be:
Create your Type:
CREATE TYPE dbo.IntTTV AS TABLE
( Id int )
Your Ids:
var ids = new List<int>
{
1,
2,
3,
}
Create a schema:
var tableSchema = new List<SqlMetaData>(1)
{
new SqlMetaData("Id", SqlDbType.Int) // I think it's Int
}.ToArray();
Create the table in C#
var table = ids
.Select(i =>
{
var row = new SqlDataRecord(tableSchema);
row.SetInt32(0, i);
return row;
})
.ToList();
Create the SQL Parameter
var parameter = new SqlParameter();
parameter.SqlDbType = SqlDbType.Structured;
parameter.ParameterName = "#Ids";
parameter.Value = table;
parameter.TypeName = "dbo.IntTTV";
var parameters = new SqlParameter[1]
{
parameter
};
Slightly change your query (this is just an example:)
string query = "select mrd.CaseList from MasterReportData mrd"
+ " inner join #ids i on mrd.Id = i.id";
public static List<string> GetCaseList(string[] masterIdList)
{
List<string> list = new List<string>();
try
{
string query = "select CaseList from MasterReportData where ";
using (SqlConnection conn = new SqlConnection(connString))
{
int i = 0;
SqlCommand cmd = new SqlCommand(query, conn);
for(i = 0; i < masterIdList.Length; i++)
{
var parm = "#ID" + i;
cmd.Parameters.Add(new SqlParameter(parm, masterIdList[i]));
query += (i > 0 ? " OR " : "") + " Id = " + parm;
}
cmd.CommandText = query;
//cmd.Parameters.AddWithValue("masterId", ***);
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
list.Add(reader[0].ToString());
}
}
}
}
catch (Exception e)
{
e.ToString();
}
return list;
}

C# search method

I'm having trouble coming up with the following search method:
public override List<Team> Search(Dictionary<string, string> prms, int pageSize, int page, out int results)
{
var tresults = new List<Team>();
string temp1 = "";
string temp2 = "";
using (SqlConnection conn = DB.GetSqlConnection())
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = #"Search";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
foreach (KeyValuePair<string, string> pair in prms)
{
temp1 = pair.Key;
temp2 = pair.Value;
}
if (temp1 == "TeamName")
{
SqlParameter p1 = new SqlParameter("TeamName", System.Data.SqlDbType.VarChar);
p1.Value = temp2;
cmd.Parameters.Add(p1);
SqlParameter p2 = new SqlParameter("CityName", System.Data.SqlDbType.VarChar);
p2.Value = null;
cmd.Parameters.Add(p2);
}
else if (temp1 == "CityName")
{
SqlParameter p1 = new SqlParameter("TeamName", System.Data.SqlDbType.VarChar);
p1.Value = null;
cmd.Parameters.Add(p1);
SqlParameter p2 = new SqlParameter("CityName", System.Data.SqlDbType.VarChar);
p2.Value = temp2;
cmd.Parameters.Add(p2);
}
SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
}
}
//results = 1 + 1;
throw new NotImplementedException("Must be implemented by class. ");
}
What I'm trying to do is basically what this test is doing:
[TestMethod]
public void SearchForTeam()
{
var dic = new Dictionary<string, string>();
int total = 0;
dic.Add("TeamName", "Patriots");
var nd = new TeamRepository();
var teams = nd.Search(dic, 100, 1, out total);
Assert.IsTrue(teams.Find(p => p.TeamName == "Patriots") != null);
}
What I'm trying to do is have my method search by either Team Name (SQL column "TeamName", value "Patriots") or by City Name (SQL column "CityName" value "Chicago", etc. I think my issues mainly are that I'm not entirely sure if I'm understanding how the dictionary works.
Also, I'm not sure how the value I'm returning should work because I am both returning an int (from the out parameter) and type List. This is all pretty new to me, so its the basics that I don't quite understand I suppose.
How about this?
public override List<Team> Search(Dictionary<string, string> prms, int pageSize, int page)
{
var tresults = new List<Team>();
using (SqlConnection conn = DB.GetSqlConnection())
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = #"Search";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
foreach (KeyValuePair<string, string> pair in prms)
cmd.Parameters.Add(new SqlParameter(pair.Key, System.Data.SqlDbType.VarChar) { Value = pair.Value });
SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
// I assume you'll use pageSize and page here?
}
}
return tresults; // I assume this is what you want to return.
}
If you don't want to use a specific column for your search, then there's no need to create a SqlParameter for that column and sets its Value to null -- just don't use that column!
Also, there's no need to have out int results. If you're returning the list of teams, then the invoker can just get the team count from the list (teams.Count). (If you are doing something else with results, then by all means ignore this paragraph.)
it's kind of hard to see what you are getting at here, I am unsure that you need a dictionary at all (would you pass in multiple records?)
personally I would do the below, the assumption is the stored procedure could handle the possibility of both parameters being populated if both are passed in completed.
public override List<Team> Search(string teamName,string cityName, int pageSize, int page)
{
var tresults = new List<Team>();
using (SqlConnection conn = DB.GetSqlConnection())
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = #"Search";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
SqlParameter p1 = new SqlParameter("TeamName", System.Data.SqlDbType.VarChar);
p1.Value = teamName;
cmd.Parameters.Add(p1);
SqlParameter p2 = new SqlParameter("CityName", System.Data.SqlDbType.VarChar);
p2.Value = cityName;
cmd.Parameters.Add(p2);
SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
while(reader.Read())
{
tresults.Add(BuildTeamFromReader(reader));
}
}
}
return tresults;
}
private Team BuildTeamFromReader(SqlDataReader reader)
{
var team = new Team();
team.TeamName = reader["TeamName"];//or whatever your column name is for team name
//ToDo other mappings
return team;
}

Pass Array Parameter in SqlCommand

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(',');
}
}

Categories

Resources