Best way to build a query with condition on code-behind? - c#

How to make this code properly? I am not satisfied with this code, I'm lost.
I give you a simple example but the query is more complexe.
Thanks in advance.
string aValue;
string queryA;
string queryB;
string finalQuery;
string queryA = #"SELECT column1 FROM table1 WHERE column1=";
queryA += aValue;
string queryB = #"SELECT column1, column2,"
if (aValue == "all"){
queryB += #"column3";
}
queryB += #"FROM table1 WHERE column1=";
queryB += #"'" +aValue+ "'";
private void exportExcel(){
// change the value with a dropdownlist
if (ddlType.selectedIndex(1))
aValue = "typeA";
else if(ddlType.selectedIndex(2))
aValue = "typeB";
else
aValue = "all";
// select the query
if (aValue == "typeA")
finalQuery = queryA;
else if (aValue == "typeB")
finalQuery = queryB;
ExecQUery(finalQuery);
}

In both Java and C# (and pretty much any other platform) you should definitely not include the values directly in the SQL. That's opening up the way to SQL injection attacks, and also makes dealing with formatting for dates, times and numbers tricky.
Instead, you should use parameterized SQL, specifying the values in the parameters. How you do that varies between Java and C#, but the principle is the same.
Another approach on both platforms is to use an ORM of some description rather than building queries by hand. For example, in .NET you might want to use a LINQ provider of some description, and in Java you might want to use something like Hibernate. Either way you get to express your queries at a higher level of abstraction than just the raw SQL.
It's hard to give much more concrete advice without knowing what platform you're really using (or database) and without a real query to look at.

One small change you can do is set value attribute of dropdownlist to typeA,TypeB, etc.. and get rid of the initial if conditions and variables.
eg:
if(ddlType.selectedValue.toString()=="typeA")
finalQuery = queryA;
if(ddlType.selectedValue.toString()=="typeB")
finalQuery = queryB;

I usually load it from a resource file. This gives you some freedom to change the queries (this in case you don't need to generate it dynamically with if blocks). In source code I use formatting ending my line with a comment line in order to avoid my IDE to concatenate or put it all in one like like:
String sql = "select " + //
" * " + //
"from "+ //
" employee " + //
"where " + //
" salary > :minSal " + //
" and startDate > :minStartDate";
And in case of conditional part I just add it with a if block. But for where statements I just add one default "1=1" in order to proceed with additional limitations, so if there is no additional limitations the query will still be valid. Suppose both where statements in the SQL bellow were added conditionally:
String sql = "select " + //
" * " + //
"from "+ //
" employee " + //
"where 1 = 1 ";
Until here you have your base SQL, valid, that means if not condition is added it will still be valid.
Suppose you will add salary limitation just in case if it is informed:
if (salary != null) {
sql += "and salary > :minSalary";
parameters.put("minSalary", salary);
}
As you can see in the same condition I add a new expression to my SQL and a parameter to a map that will be used later in execute to set the parameters to the query, that avoids you to create a second if statement just to set this parameter.
Another approach that you could take is build the entire SQL and before the execution ask for the prepared statement which parameters it needs as input and provide them. In java you can do it with:
http://download.oracle.com/javase/1.4.2/docs/api/java/sql/PreparedStatement.html#getParameterMetaData%28%29
I know that is not the case but if ORM is used, is common to have Builders for queries and this turns this task much easier, for example in Hibernate you could have something like:
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "F%")
.addOrder( Order.asc("name") )
.addOrder( Order.desc("age") )
.setMaxResults(50)
.list();
As it is documented at:
http://docs.jboss.org/hibernate/core/3.3/reference/en/html/querycriteria.html
That means you could do this:
Criteria c = sess.createCriteria(Cat.class)
.addOrder( Order.asc("name") )
.addOrder( Order.desc("age") )
.setMaxResults(50);
if (name != null) {
c.add( Restrictions.like("name", name);
}
List cats = c.list();

Related

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

Is this dynamic query vulnerable to SQL injection?

I am rather new to SQL in C# and I need some advice on SQL injection.
public System.Linq.IQueryable findBy(List<String> lWhere)
{
string sWhere;
foreach (var (sQueryPart, i) in lWhere.Select((Value, i) => (Value, i)))
{
if (i == 0)
{
sWhere = sQueryPart;
}
else if (i == 1)
{
sWhere += " = " + sQueryPart;
}
else if (i % 2 == 0)
{
sWhere += " and " + sQueryPart;
}
else
{
sWhere += " = " + sQueryPart;
}
}
return this.TABLE.FromSqlRaw("SELECT * FROM TABLE WHERE {0}", sWhere);
}
This method gets a list with entries like {"COLUMN1", "VALUE1" , "COLUMN2", "VALUE2"...}
After that, I build my Where clause using this list and enter it into the select statement.
First of all, the list might get replaced by a dictionary, actually I am pretty sure of that.
Secondly my question, is this safe against SQL injection? There shouldn't be user input other then using the method in program code, but no manual entries after that.
EDIT: it is important that I do not know the number of where clauses used, it could range from 1 to 4
If you are building your SQL query manually by concatenating strings, you are vulnerable to SQL injection. Full stop.
I don't understand why you are even doing this, as your code implies you are using Entity Framework. Which adds methods on your database entities to allow you to dynamically chain as many .Where() clauses as you require, precisely to remove the need for you to write SQL yourself, for example:
var results = dbContext.Table
.Where(t => t.Column1 == "foo")
.Where(t => t.Column2 == 42);
which will generate and execute SQL along the lines of:
select *
from Table
where Column1 = 'foo'
and Column2 = 42;
If you are using Entity Framework properly, you should almost never have to write any SQL yourself. EF will generate it for you, in a way that is not susceptible to SQL injection.
As long as the List of strings is not formed from user input you are safe from SQL Injection; however, this is still bad code:
You should not do select * From. That’s bad because if the table gets a new column that you don’t need -and it may very well be huge binary data- you will end up getting it without needing it, slowing your app considerably.
It’s better to create a stored procedure with parameters for filtering the data. That way, you protect your code from SQL Injection and you can still branch your SQL statement to perform the filtering based on the parameters passed.
If at any point in time your List of Strings is gathered from user input, your code will be vulnerable.

Not sure if this is even possible, using if statement inside string sql variable

I have a long sql query string saved into a variable.
1 sSQL ="select a bunch of stuff from multiple tabled" +
2 "where conditions are met" +
3 "and other conditions are met" +
4 "order by these columns";
What I now need to do is to add a line between 3 and 4 that is an if statement. Or I need to figure out some other method that doesn't involved me putting two massive sql blocks of code which is mostly redundant except for one single line.
The Code needed:
if (proSeries)
{
"unique sql code to add only if proSeries is true" +
}
It doesn't like the block inside the string. The only other way I can think of doing it is just copying the whole sql string into two separate variables and putting them into the if/else.
You can't have conditional logic within a variable declaration. You can however, take advantage of StringBuilder:
StringBuilder sqlText = new StringBuilder();
sqlText.AppendLine("select a bunch of stuff from multiple tabled");
...
if (proSeries)
{
sqlText.AppendLine("unique sql code to add only if proSeries is true");
}
sqlText.AppendLine("order by these columns");
string finalText = sqlText.ToString();
You could easily wrap that in a function call if you will run it a lot.
When it comes to having multiple conditions, I sometimes do the following, which I consider quite readable:
sSQL = "select a bunch of stuff from multiple tables " +
"where conditions are met " +
"#AND_PROSERIES_CRITERIA# " +
"and other conditions are met " +
"order by these columns ";
// Build all criteria strings
String sProSeriesCriteria = (proSeries) ? "and ....." : "";
// Insert all criteria
sSQL = sSQL.Replace("#AND_PROSERIES_CRITERIA#", sProSeriesCriteria);
Well, the effect is better with more criteria, e.g. select all orders
from all suppliers or just certain ones AND
for all products or just certain ones AND
for which exist or not exist returns in another table
...
You get the idea. With many such optional criteria, you still write a query that is readable and then you work out the parts.

SQL query formation dynamically, using string.format

I have a serious problem of formatting queries, it may result in SQL injection too, I saw some similar qstns, but not sure how could i use it in C# as I am new to it. I use c#,Odbc command
I have 3 strings like
qry ="select description from TableA" , qryOrder = " order by description" , qryAppend = " where ID = '{0}' order by description\", _selectedPlantID" provided _selectedId is another variable, Now I want to use these variables to form diff queries at different scenarios, for eg, qry + qry order , or qry + qryAppend.
Since _selectedPlantId is also needed, I use string.Format as :
_cmd.CommandText = "string.Format(\"" + qry + qryAppend + ")";
But its not working. any solution ?
Error is SQL syntax error with quotes
thanks in advance !!
Simply put, this should make it work. You'll need two variables (bool), I'll explain later why:
var shouldOrder = true;
var shouldAppend = true;
_cmd.CommandText = String.Format(
"{0} {1} {2}",
qry,
shouldOrder ? qryOrder : String.Empty,
shouldAppend ? qryAppend : String.Empty
);
These two variables (shouldOrder and shouldAppend) will help you with the "diff queries at different scenarios" as you've said.
Providing these variables with true or false will change what text goes into the String.Format and will change query accordingly.
So, if you use shouldOrder = false; the query command won't get the order part. Setting shouldAppend = false; will avoid including the extra part (append) into the SQL command.
Now, be careful!
This won't solve your SQL injection problem. I've just shown a quick fix.
To avoid SQL injections, you'll have to change your SQL command and you cannot use String.Format anymore.
To understand how to do that, take a look into DGibbs comment.

auto SQL field's quotation marks by type in c#

Suppose that I want to create an SQL SELECT statement dynamically with reflection on primary key. I search in the table for primary keys and then, I make the statement.
Problem is, I don't know the type of fields that compose the primary key before getting them. So, if it's a string or date, I must add quotation marks but not if it's an int.
Atm, I am doing like that :
var type = field.GetType().Name;
if (type.ToLower().StartsWith("string") || type.ToLower().StartsWith("date"))
{
field = "\"" + field + "\"";
} else if (type.ToLower().StartsWith("char"))
{
field = "\'" + field + "\'";
}
With this code, I can handle some SQL types but there are a lot more.
My problem is that it's combined with LinQ. I got a DataContext object and a generic type table from the context. And context.ExecuteQuery only allows parameters to be passed has values. I also tried with Dynamic LinQ but I got the same problem
Does anyone know a better solution?
That is simply the wrong way to write SQL. Parameterize it and all these problems evaporate (as do problems with "which date format to use", etc. And of course the biggie: SQL injection.
Then it just becomes a case of adding #whatever into the TSQL, and using cmd.Parameters.AddWithValue("whatever", field) (or similar).
Update (from comments): since you mention you are using DataContext.ExecuteQuery, this becomes easier: that method is fully parameterized using the string.Format convention, i.e.
object field = ...;
var obj = db.ExecuteQuery<SomeType>(
"select * from SomeTable where Id = {0}", field).ToList(); // or Single etc
No string conversions necessary.
(the last parameter is a params object[], so you can either pass multiple discreet terms, or you can populate an object[] and pass that, if the number of terms is not fixed at compile-time; each term in the array maps by (zero-based) index to the {0}, {1}, {2}... etc token in the query)
Have you tried with parameters? For instance if you are using SQLServer as a database and you want to do this query:
"SELECT * FROM someTable WHERE id = " + field;
Then you should use sometething like this:
"SELECT * FROM someTable WHERE id = #field"
and add parameter to your command:
SqlParameter param1 = new SqlParameter("#field", field);
command.Parameters.Add(param1);
EDIT: Watch out that for different database providers the syntax for the SQL query is different, the same for the Access would be
"SELECT * FROM someTable WHERE id = ?";
command.Parameters.AddWithValue("field", field);

Categories