Parameterize query with IN keyword c# - c#

I am in a application where Parameterized Sql queries are not written. Below is code block
public List<MyClass> GetData(int Id, IEnumerable<string> state)
{
using (var dataContext = new DataContext(_connectionString))
{
var query = new StringBuilder("SELECT * FROM table");
query.Append(" Id = ");
query.Append(Id);
query.Append(" AND state IN ('");
query.Append(string.Join("','", state));
query.Append("')");
return dataContext.ExecuteQuery<MyClass>(query.ToString()).ToList();
}
I am refactoring code using parameterized query like this :
public List<MyClass> GetData(int Id, IEnumerable<string> state)
{
using (var dataContext = new DataContext(_connectionString))
{
var statestring = new StringBuilder("'");
statestring.Append(string.Join("','", state));
statestring.Append("'");
string myStates= statestring.ToString();
string query = "SELECT * FROM table WHERE Id ={0} AND state IN ({1})";
return dataContext.ExecuteQuery<MyClass>(query, new object[] {Id, myStates}).ToList();
}
}
I get no data on running this query. On debugging i found my query is getting formed like this
SELECT * FROM table WHERE Id ={0} AND state IN ({1}) where in ({1})
For state I see data as "'error',' warning'".
In sql server I run query like this
SELECT * FROM table WHERE Id =34 AND state IN ('error','warning').
Do i need to remove " " around mystate? I tried removing " using trim method and assigning it back to string but it didn't work. I can still see double quotes.
myStates = myStates.trim('"');
How can parameterize my query better without using any string builder for the same

Alternative suggestion: dapper...
int x = ...;
int[] y = ...
var items = connection.Query<MyClass>(
"SELECT * FROM table WHERE X = #x AND Y in #y", new {x,y}).AsList();
Dapper will deal with this for you, using an appropriate query for 0, 1, or many items, including (optional configuration options) padding the parameters to avoid query plan saturation (so when you have large lists, you use the same query and query-plan for 47 items, 48 items and 49 items, but possibly a different query for 50), and using string_split if it is supported on your server.

To parameterize the in clause every case has to be an individual parameters. So the in clause has to reflect that.
See this similar question: How to pass sqlparameter to IN()?
public List<MyClass> GetData(int Id, IEnumerable<string> state)
{
using (var dataContext = new DataContext(_connectionString))
{
var stateParameterNumbers = Enumerable.Range(1, state.Count())
.Select(i => string.Format("{{{0}}}", i));
var stateParameterString = string.Join(",", stateParameterNumbers);
string query = "SELECT * FROM table WHERE Id ={0} AND state IN (" + stateParameterString + ")";
return dataContext.ExecuteQuery<MyClass>(query, new object[] { Id }.Concat(state).ToArray()).ToList();
}
}

I think that you should change the way you pass the parameters:
return dataContext.ExecuteQuery<MyClass>(query, Id, stateString).ToList();
For reference please have a look at the signature of this method, which can be found here.

Related

ADO.NET query returning nothing even if the item available

I am using ADO.NET for querying the SQL Server database. I am trying to get items if present on the table.
My query is executing but returning nothing even if there is.
Here is my code:
public List<string> GetRecords(List<string> itemList)
{
itemList.Add("100");
string list = string.Join(",", itemList.Select(x => string.Format("'{0}'", x)));
string query = #"SELECT Id FROM Employees WHERE Id In (#list)";
using (SqlCommand sqlCommand = new SqlCommand(query,connection))
{
sqlCommand.Parameters.AddWithValue("#list", list);
sqlDataReader = sqlCommand.ExecuteReader();
while (sqlDataReader.Read())
{
employeeList.Add(Convert.ToString(database.Sanitise(sqlDataReader, "Id")));
}
}
}
There are three items in the list the employee with ID=100 is available in the table but the other two's are not. but still the query returning nothing.
SQL profiler showing me this query:
exec sp_executesql N'SELECT
Id
FROM
Employees
WHERE
Id In (#list)',N'#list nvarchar(29)',#list=N'''50'',''23'',''100'''
SQL Server will not interpret your concatenated list as actual code. It remains data always, so it's just one big text string of numbers. That is never going to match a single row.
Instead, use a Table Valued Parameter.
First create a table type in your database, I usually keep a few useful ones around.
CREATE TYPE dbo.IdList (Id int PRIMARY KEY);
Then create a DataTable and pass it as a parameter.
public List<string> GetRecords(List<string> itemList)
{
var table = new DataTable { Columns = {
{ "Id", typeof(int) },
} };
foreach (var id in itemList)
table.Rows.Add(id);
const string query = #"
SELECT e.Id
FROM Employees e
WHERE e.Id IN (SELECT l.Id FROM #list l);
";
using (var connection = new SqlConnection(YourConnString)) // always create and dispose a new connection
using (var sqlCommand = new SqlCommand(query,connection))
{
sqlCommand.Parameters.Add(new SqlParameter("#list", SqlDbType.Structured) {
Value = table,
TypeName = "dbo.IdList",
});
connection.Open();
using (var sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
employeeList.Add((string)sqlDataReader["Id"]);
}
}
}
}
Note also:
using on all SQL objects.
Do not cache a connection object. Create when you need it, dispose with using.
I don't know what your Sanitize function does, but it probably doesn't work. Sanitizing database values correctly is hard, you should always use parameterization.
AddWithValue is a bad idea. Instead specify the parameter types (and lengths/precision) explicitly.

Sql/Dapper: How do perform LIKE in WHERE clause for array of values?

I have a table with a lot of employees in it, every person has a Name column with their full name.
I then want to do a query similar to this when searching for people:
SELECT * FROM Employees WHERE Name LIKE '%' + #value1 + '%' AND Name LIKE '%' + #value2 +'%' AND so forth...
for an arbitrary array of values.
My Dapper code would look something like this:
public IEnumerable<Employee> Search(string[] words)
{
using var connection = CreateConnection();
connection.Query<Employee>("SELECT * etc.", words);
}
Is there ANY way to do this with SQL without resorting to string concatenation, and the risk of SQL Injection attacks that follows?
Caveat: I don't know how Dapper actually passes an array to the query, which limits my creative ideas for working around this :-D
And also: Changing the Table structure is, unfortunately, out of the question. And I'd rather avoid fetching every single person into .Net memory and doing the filtering there.
Is there ANY way to do this with SQL without resorting to string concatenation, and the risk of SQL Injection attacks that follows?
Because the set of where conditions is not fixed you will need to build the query dynamically. But that does not mean you cannot parameterise the query, you just build the parameter list alongside building the query. Each time a word from the list add to the condition and add a parameter.
As Dapper doesn't directly include anything that takes a collection of DbParameter, consider using ADO.NET to get an IDataReader and then Dappter's
IEnumerable<T> Parse<T>(this IDataReader reader)
for the mapping.
Such a builder would be very roughly
var n = 0;
for (criterion in cirteria) {
var cond = $"{crition.column} like #p{n}";
var p = new SqlPatameter($"#p{n}", $"%{crition.value}%";
conditions.Add(cond);
cmd.Parameters.Add(p);
}
var sql = "select whetever from table where " + String.Join(" and ", conditions);
cmd.CommandText = sql;
var reader = await cmd.ExecuteReaderAsync();
var res = reader.Parse<TResult>();
For performance reasons, it's much better to do this as a set-based operation.
You can pass through a datatable as a Table-Value Parameter, then join on that with LIKE as the condition. In this case you want all values to match, so you need a little bit of relational division.
First create your table type:
CREATE TYPE dbo.StringList AS TABLE (str varchar(100) NOT NULL);
Your SQL is as follows:
SELECT *
FROM Employees e
WHERE NOT EXISTS (SELECT 1
FROM #words w
WHERE e.Name NOT LIKE '%' + w.str + '%' ESCAPE '/' -- if you want to escape wildcards you need to add ESCAPE
);
Then you pass through the list as follows:
public IEnumerable<Employee> Search(string[] words)
{
var table = new DataTable{ Columns = {
{"str", typeof(string)},
} };
foreach (var word in words)
table.Rows.Add(SqlLikeEscape(word)); // make a function that escapes wildcards
using var connection = CreateConnection();
return connection.Query<Employee>(yourQueryHere, new
{
words = table.AsTableValuedParameter("dbo.StringList"),
});
}

How should I pass an array to the FIELD statement using Dapper, c#

I am trying to pass an array as a parameter using Dapper.
My array of values must go into the FIELD section.
I tried to join the array elements into a String and pass it. Still doesn't work.
Guid[] myArr = Ids.ToArray(); // Ids are List<Guid>
var script = #"SELECT * FROM table WHERE Id in #Ids ORDER BY FIELD(Id, #param)";
using (var connection = database.Connection)
{
return connection.Query<MyDataType>(script, new {Ids = Ids, param = myArr}).ToList();
}
This query is just doing an Order By Id. I also passed in param = Ids. Still doesn't work.
Convert the list into array in parameter list of dapper.
var sqlQuery = "Select * from table Where Columnname IN #periodIdStr";
var result = dapperUOW.Connection.Query<Entity>(sqlQuery ,
param: new { periodIdStr = periodIds.ToArray() }, transaction: dapperUOW.Transaction).ToList();
According to this question SELECT * FROM X WHERE id IN (...) with Dapper ORM you should be able to do a WHERE in with dapper but the limit of the number of items in the array is something to watch out for.
Then you could also break out the ORDER BY FIELD SQL and use linq to do an OrderBy on your results.
Edit:
Would this work?
Guid[] myArr = Ids.ToArray(); // Ids are List<Guid>
var script = #"SELECT * FROM table WHERE Id in #Ids)";
using (var connection = database.Connection)
{
var myDataTypeObjects = connection.Query<MyDataType>(script, new {Ids = Ids}).ToList();
myDataTypeObjects = myDataTypeObjects.OrderBy(x => Ids.IndexOf(x.Id)).ToList();
return myDataTypeObjects;
}

Get table data from dynamically selected table in LINQ C#

I obtain a table reference as such:
public static void MyDataGrabbingClass<T>(MyModelCls Model) where T : class
{
DataContext dc = new DataContext(Config.ConnectionString);
//var ITable = (Devart.Data.Linq.ITable)dc.GetType().GetProperty(tableName).GetValue(dc, null);
//var table = dc.GetTable(ITable.GetType());
//var dataModel = dc.Mapping;
//Type tableType = ITable.GetType();
//var t = dc.Mapping.MappingSource.GetModel(typeof(DataContext)).GetMetaType(tableType);
var table = dc.GetTable<T>();
}
I want to then select the specific columns of data using:
var Query = from c in table where Model.DateToColName < Model.DateTo select (Model.ColSelections);
obviously I need to somehow map the table column, and not use Model.DateToColName in the where clause, how do I do this?
In other words, with my dynamically chosen table, how do i get the column data from a string column name
Unfortunately I don't think you'll be able to construct a dynamic query in LINQ (at least not easily). Take a look at Dapper.NET - it's a "simple object mapper for .NET" that might work for you, created by the makers of StackOverflow. For example, using Dapper.NET, your method might look like:
public static void MyDataGrabbingClass<T>(MyModelCls Model) where T : class
{
using (SqlConnection conn = new SqlConnecrion(Config.ConnectionString)
{
conn.Open();
string tableName = ...;
string dateToColumnName = ...;
// depending on how dateToColumnName is constructed, ensure it is not a SQL-injection risk
if (tableName.Any(c => !char.IsLetterOrDigit(c))
throw new ArgumentException("Invalid table name.");
if (dateToColumnName.Any(c => !char.IsLetterOrDigit(c))
throw new ArgumentException("Invalid column name.");
// Query is a
var results = conn.Query<T>("SELECT * FROM [" + tableName + "] WHERE [" + dateToColumnName + "] < #DateTo", new { DateTo = someDate });
...
}
}
Security concern: Dynamic SQL can be susceptible to SQL injection - so be sure that the dateToColumnName variable is not from user-input, or is sanitized or validated appropriately.

Parametized SQL Query with unlimited parameters

I've heard of parametized sql queries a long time ago but I never really gave any attention to it as I'm used to writing full sql statements. I know it improves security against sql injection so now might be a very good time to adapt change even if it's too late. I got this one from this site http://www.codinghorror.com/blog/2005/04/give-me-parameterized-sql-or-give-me-death.html. So all credits go to the author. I've noticed all examples of parametized sql queries have paremeter count limitations. An example is the one shown below.
SqlConnection conn = new SqlConnection(_connectionString);
conn.Open();
string s = "SELECT email, passwd, login_id, full_name FROM members WHERE email = #email";
SqlCommand cmd = new SqlCommand(s);
cmd.Parameters.Add("#email", email);
SqlDataReader reader = cmd.ExecuteReader();
This is just equivalent to
SELECT email, passwd, login_id, full_name FROM members WHERE email = 'x';
I've learned programming all on my own through reading online tutorials and I really got nobody to help me on this. The reason I got stuck with full sql statement queries is the fact that I can't figure out how to do parametized sql queries with unlimited parameters. Is there any way to do this? The example only accepts 1 parameter which is the 'email' field. And it selects 4 fields from the given sql statement. My question is... is there any way we can do parametized sql queries with 5, 6, 7, or 100 selected fields, as well as the conditions under the WHERE clause? If this is possible, it will be particularly helpful when using INSERT statement. Thank you very much.
This example is in C#, but any VB.NET or same C# implementations are greatly appreciated. Thanks.
One possible solution would be to pass the parameters name and value using a Dictionary object. A Dictionary object holds a collection of keys and values, and this is exactly what a single SqlParameter is - a single Key/Value container in the form:
Key = #Value
A collection can hold an arbitrary count of items, for example:
new Dictionary<String, Object>
{
{ "#Name", "Anonymous" },
{ "#Age", 25 },
{ "#Street", "Highway" },
{ "#Number", "1001" },
{ "#City", "NoName" }
}
In the example above, the key is of type String and the value of type Object. Object allows parameter values of arbitrary types (the explanation comes later in the code examples).
One possibility to create dynamic SQL statements would be:
extract the repetitive tasks, for example the creation of the SqlCommand and the passing of the parameters to the SqlCommand variable
create parametrised dynamic SQL query string for the wanted SQL command
The code can look like this:
// extract all repetitive tasks
// Create an array of SqlParameters from the given Dictionary object.
// The parameter value is of type Object in order to allow parameter values of arbitrary types!
// The .NET Framework converts the value automatically to the correct DB type.
// MSDN info: http://msdn.microsoft.com/en-us/library/0881fz2y%28v=vs.110%29.aspx
private SqlParameter[] dictionaryToSqlParameterArray(Dictionary<string, object> parameters)
{
var sqlParameterCollection = new List<SqlParameter>();
foreach (var parameter in parameters)
{
sqlParameterCollection.Add(new SqlParameter(parameter.Key, parameter.Value));
}
return sqlParameterCollection.ToArray();
}
// sqlQuery is the complete parametrised query
// for example like: INSERT INTO People(Name, Age, Street) VALUES(#Name, #Age, #Street)
private SqlCommand createSqlCommand(String sqlQuery, Dictionary<String, object> parameters)
{
SqlCommand command = new SqlCommand(sqlQuery);
command.Parameters.AddRange(dictionaryToSqlParameterArray(parameters));
return command;
}
Now a call with dynamic count of parameters will look like this:
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
string sqlQuery = "SELECT email, passwd, login_id, full_name FROM members WHERE email = #email AND name = #name";
// using the newly created method instead of adding/writing every single parameter manually
SqlCommand command = createSqlCommand(sqlQuery, new Dictionary<String, Object>
{
{ "#email", "test#test.com" },
{ "#name", "test" }
});
SqlDataReader reader = command.ExecuteReader();
It is a good start, but this is still not very dynamic, since we need to write the complete query every time (we save only the typing of command.Parameters.Add). Let's change this in order to type even less:
// create a parametrised SQL insert command with arbitrary count of parameters for the given table
private SqlCommand createSqlInsert(String tableName, Dictionary<String, object> parameters)
{
// the sql insert command pattern
var insertQuery = #"INSERT INTO {0}({1}) VALUES({2})";
// comma separated column names like: Column1, Column2, Column3, etc.
var columnNames = parameters.Select (p => p.Key.Substring(1)).Aggregate ((h, t) => String.Format("{0}, {1}", h, t));
// comma separated parameter names like: #Parameter1, #Parameter2, etc.
var parameterNames = parameters.Select (p => p.Key).Aggregate ((h, t) => String.Format("{0}, {1}", h, t));
// build the complete query
var sqlQuery = String.Format(insertQuery, tableName, columnNames, parameterNames);
// debug
Console.WriteLine(sqlQuery);
// return the new dynamic query
return createSqlCommand(sqlQuery, parameters);
}
// create a parametrised SQL select/where command with arbitrary count of parameters for the given table
private SqlCommand createSqlWhere(String tableName, Dictionary<String, object> parameters)
{
// the sql select command pattern
var whereQuery = #"SELECT * FROM {0} WHERE {1}";
// sql where condition like: Column1 = #Parameter1 AND Column2 = #Parameter2 etc.
var whereCondition = parameters.Select (p => String.Format("{0} = {1}", p.Key.Substring(1), p.Key)).Aggregate ((h, t) => String.Format("{0} AND {1}", h, t));
// build the complete condition
var sqlQuery = String.Format(whereQuery, tableName, whereCondition);
// debug
Console.WriteLine(sqlQuery);
// return the new dynamic query
return createSqlCommand(sqlQuery, parameters);
}
Now the creation of a SELECT command will look like this:
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
// specify only table name and arbitrary count of parameters
var getPersonSqlCommand = createSqlWhere("People", new Dictionary<String, Object>
{
{ "#Name", "J.D." },
{ "#Age", 30 }
});
SqlDataReader reader = getPersonSqlCommand.ExecuteReader();
The createSqlWhere method will return an initialized SqlCommand with the query:
SELECT * FROM People WHERE Name = #Name AND Age = #Age
The INSERT will also be short:
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
// specify only table name and arbitrary count of parameters
var addPersonSqlCommand = createSqlInsert("People", new Dictionary<String, Object>
{
{ "#Name", "Anonymous" },
{ "#Age", 25 },
{ "#Street", "Test" }
});
addPersonSqlCommand.ExecuteNonQuery();
The corresponding SQL command will look like this:
INSERT INTO People(Name, Age, Street) VALUES(#Name, #Age, #Street)
Other SQL commands like DELETE, UPDATE and so on can be created in the same way. New parameters should be added only in one place - in the dictionary.
Admittedly the initial effort is more than just writing one method, but it will pay off if the new methods are used more than once (a couple of times) - for example five parametrised selects and/or inserts on five different tables with different parameters, which is certainly always the case in small and average sized software projects.
Just create a method like :
public void unlimQuery(string query,params object[] args)
{
SqlConnection conn = new SqlConnection(_connectionString);
conn.Open();
string s =query;
SqlCommand cmd = new SqlCommand(s);
For(int i=0;i< args.Length;i++)
cmd.Parameters.Add("#param"+i, args[i]);
SqlDataReader reader = cmd.ExecuteReader();
}
Example :
unlimQuery("INSERT INTO CUSTOMERS(ID,NAME,AGE,ADRESS,COUNTRY) VALUES(#param0,#param1,#param2,#param3,#param4)",5,"Ali",27,"my City","England");
Explanation :
the params keyword in c# gives you the possibility to insert unlimited arguments of the indicated type, so the arguments added (5,"Ali",27,"my City","England") will be casted to an array of objects then passed to the Method
inside the method you'll get an array of objects, so for each object you create a parameter, his alias is #paramX where X is the index of the argument (in the params array), then sqlCommad will replace each alias by its value defined in the cmd.Parameters.Add("#param"+i, args[i]) clause
so #param0 => 5, .....
i think you just have to change this
cmd.Parameters.Add("#email", email);
to
cmd.Parameters.AddWithValue("#email", email);

Categories