Help with Parameterizing SQL Query for C# with possible null values - c#

I need help with parameterizing this query.
SELECT *
FROM greatTable
WHERE field1 = #field1
AND field2 = #field2
The user should be able to search for any of the 2 fields, and the user also should be able to search if the field2 has null values.
var query = "theQuery";
var cm = new SqlCommand(cn, query);
cm.AddParameter("#field1", "352515");
cm.AddParameter("#field2", DBNull.Value);
// my DataTable here is having 0 records
var dt = GetTable(cm);
[Edit]
What is the best alternative?
Keep CommandText constant so the plan in Sql be reused
WHERE (field2 = #field2 OR #field2 IS NULL)
Change CommandText dynamically based on the values introduced by the user.
WHERE field2 IS NULL
I'm not just thinking in one field, it could be various.

You can use AND (#field2 IS NULL OR field2 = #field2) to make the query return all rows without checking field2 (to allow you to pass DbNull from your code.
The complete query would be something like this:
SELECT *
FROM greatTable
WHERE field1 = #field1
AND (#field2 IS NULL OR field2 = #field2)
Note that when using this method there might be a performance-hit because of indexing. Take a look at this article for details.

A dirty method:
isnull(field2,0) = isnull(#param2,0)
have the isnull something that would never be in the field or param

A good question, but in practice I had no time such problem. If I use AddParameter then I also construct the string for the query in the same code fragment. So if I know, that now I should search for "#field2" equal to NULL I decide whether to construct
string query = "SELECT * " +
"FROM greatTable " +
"WHERE field1 = #field1";
or
string query = "SELECT * " +
"FROM greatTable " +
"WHERE field1 = #field1 AND field2 IS NULL";
and add only one parameter
cm.AddParameter("#field1", "352515");
So I have no time the problem which you describe.
UPDATED: What is the best (or the only correct) way is users input is NULL you should decide based on the context. In the most cases "AND field2 IS NULL" is not needed, but I read your question so, that in your special case the user can explicitly choose not "" (empty string), but a NULL value.

Related

Filter data from database by passing multiple nullable values

I have a table with n numbers of column, and i want to filter data by n numbers of nullable parameters,
instead of writing n times if else condition is there any way to resolve this problem either in c# (Linq,Entity framework) or in SQL with queries.
if any one have any solution please give the solution with an example.
Thanking you.
sure, you can have optional parameters in that sql.
The way you do this? You don't include the parameters in the sql, and then ONLY add the parameters as you need them! That way, you don't need all those extra conditions in the sql that is the condition, and then also the test for the #Param = null.
So, lets assume that I can search for City, or City + HotelName. And lets toss in a [x] Only search for Active Hotels. Or we search just for Hotelname. Or all 3 values!
As you WELL note, this becomes a harry ball of parameters that has to deal with only 3 choices (6 possible permutations). I can only imagine how bad this gets if you have 5 or 6 possible and optional value.
so, the simple solution? Well, we split the sql into a the base query, and then add the parameters on the fly. We STILL want (and get) strong type parameter checking, and thus get sql injection protection which of course is a important goal here.
We thus have this setup:
And the search then does this:
public void loadgrid()
{
string strSQL;
string strWhere;
strSQL = "select ID, FirstName, LastName, HotelName, City, Province from tblHotels";
strWhere = "";
using (SqlCommand cmdSQL = new SqlCommand(strSQL, new SqlConnection(My.Settings.Test3)))
{
if (txtHotelName.Text != "")
{
// search for hotel name
strWhere = "(HotelName = #HotelName)";
cmdSQL.Parameters.Add("#HotelName", SqlDbType.NVarChar).Value = txtHotelName.Text;
}
if (txtCity.Text != "")
{
if (strWhere != "")
strWhere += " AND ";
strWhere += "(City = #City)";
cmdSQL.Parameters.Add("#City", SqlDbType.NVarChar).Value = txtCity.Text;
}
if (chkOnlyActive.Checked == true)
{
if (strWhere != "")
strWhere += " AND ";
strWhere += strWhere + "(HotelActive = #Active)";
cmdSQL.Parameters.Add("#Active", SqlDbType.Bit).Value = 1;
}
if (strWhere != "")
cmdSQL.CommandText = strSQL + " WHERE " + strWhere;
cmdSQL.Connection.Open();
DataTable rstData = new DataTable();
rstData.Load(cmdSQL.ExecuteReader);
ListView1.DataSource = rstData;
ListView1.DataBind();
}
}
So note how we simply build up the where clause. And you note that there is NOTHING that prevents us from changing the sql command text - and we are also 100% able to add parameters on the fly (adding them does not force a check against the sql - only at execute time.
As a result? We can add 5 more criteria. They are optional, they don't require us to make a huge long sql query with a gazillion parameters that we may will not want to use or even need.
And as above shows, we there are NEVER sting concatenation of the user inputs - they ALWAYS are used ONLY with parameter values.
So, for any text box, check box, combo box or whatever? We simply ignore them when they are not filled out. They are thus all optional, and quite much ignored in our code. The above setup thus would allow us with easy to add 2 or 5 more optional parameters.
Note in above, we always "check" if the where clause already has some value - and if so, then we add the " AND " clause in front. We could I suppose use " OR " here, but it depends on the kind of search you want.
I nice 'trick' that can be used in both SQL statements and LINQ queries is to allow nulls on your query params and then check for a matching value or null on each parameter.
We make our params nullable and check each against their respective field/property or for null.
Basically, we tell the query to give us all records where the input parameter matches the property value OR if the input parameter is null we short circuit that param essentially causing our query to ignore that param. This effectively gives a parameter that is treated as optional when it's null and not optional otherwise.
Using this method you can easily add more optional parameters.
IList<ThingToQuery> things = new List<ThingToQuery>();
things.Add(new ThingToQuery(){ Property1 = "Thing1", Property2 = 100, Property3 = new DateTime(2001,1,1)});
things.Add(new ThingToQuery() { Property1 = "Thing2", Property2 = 100, Property3 = new DateTime(2002, 2, 2) });
things.Add(new ThingToQuery() { Property1 = "Thing3", Property2 = 300, Property3 = new DateTime(2003, 3, 3) });
// query sample #1 - prepare params
string queryParam1 = "Thing1";
int? queryParam2 = 100;
DateTime? queryParam3 = null;
// in our query we check for a matching value or if the param is null
List<ThingToQuery> results = things.Where(t => (t.Property1 == queryParam1 || queryParam1 == null)
&& (t.Property2 == queryParam2 || queryParam2 == null)
&& (t.Property3 == queryParam3 || queryParam3 == null)
).ToList();
// query sample #1 results
// Thing1, 100, 1/1/2001 12:00:00 AM
// query sample #2 - prepare params
string queryParam1 = null;
int? queryParam2 = 100;
DateTime? queryParam3 = null;
// query sample #2 results
// Thing1, 100, 1/1/2001 12:00:00 AM
// Thing2, 100, 2/2/2002 12:00:00 AM
A simple SQL example...
SELECT * FROM Users u
WHERE (u.UserName = #UserName OR #UserName IS NULL)
OR (u.FavoriteColor = #FavColor OR #FavColor IS NULL)

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);

PetaPoco not returning expected data

I have a BIT data type on one of my columns.
I have written a query that does SELECT * FROM TABLE WHERE BITCOLUMN <> #0
It works fine if I pass in 1 or 0 but if I pass in 3 PetaPoco doesn't return the results I expect.
Executing the SQL in a query window does return all records when I use 3 as the parameter value.
Any ideas?
UPDATE: If I use string SQL = "SELECT * FROM TABLE WHERE BITCOLUMN <> " + MethodParam; This returns data as expected.
Could you tell me what result you expecting? According to MSDN, A bit column can either be 1, 0 or null. It does not make senses to me when you pass 3 to it, since it will select all rows.
And my quick test shows that Petapoco behaves as expected.
using (var database = new Database("sql"))
{
string sql = "SELECT COUNT(*) FROM TBLTESTBIT WHERE BITCOLUMN <> " + "3";
var test = database.ExecuteScalar<long>("SELECT COUNT(*) FROM TBLTESTBIT WHERE BITCOLUMN <> #0", 3);
var test2 = database.ExecuteScalar<long>(sql);
Console.WriteLine(test == test2); // this output true
Console.Read();
}
I think it will be because when you use a parametrised sql string, you are passing in int with a value of 3 in as one of the args. PetaPoco will then create an IDataParameter for that argument and the DbType will be set to DbType.Int32 by default since PetaPoco has no idea what the underlying table column type is.
Use new Sql() not Append.
You should use Query = new PetaPoco.Sql( over Query = PetaPoco.Sql.Append

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

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();

How to add a "select all" value to a parametrized query in C# and MS SQL

I have the following code:
As you can see, i need to pass a parameter to Field2, but i also need that parameter to be ablo to handle the ""all values" option, for example, if i assign "foo" to the parameter, the query will return every record where Field2 = "foo"... but i also want to be able to pass a wildcard, or something to tell that parameter to give all values as result.
MyDataset dataset = new MyDataset();
SqlConnection objConnection = new SqlConnection(_connectionstring);
SqlDataAdapter objDataAdapter = new SqlDataAdapter();
objDataAdapter.SelectCommand = new SqlCommand();
objDataAdapter.SelectCommand.Connection = objConnection;
objDataAdapter.SelectCommand.CommandText =
"SELECT Field1, Field2, Field3 FROM Table WHERE (Field2 = #Field2)";
objDataAdapter.SelectCommand.CommandType = CommandType.Text;
objDataAdapter.SelectCommand.Parameters.AddWithValue("#Field2", txtBoxField2.Text);
objDataAdapter.Fill(dataset.Table);
this.DataContext = dataset.Table.DefaultView;
Thank you in advance.
One way to do this is to use nullable parameters:
SELECT Field1, Field2, Field3
FROM Table
WHERE (#Field2 IS NULL OR Field2 = #Field2)
but you need to be aware that this can lead to incorrectly cached query plans in some circumstances where there are many parameters. If you are using SQL Server 2005+, this can be mitigated to a large ectent using OPTIMIZE FOR.
Also, make sure your statistics are up to date.
If you're building up the SQL like that within your C# code, then just put an if condition in your C# logic to not include the WHERE clause in the instance you want to return all records. This would create a different SQL statement, so would be OK for performance/execution plan-wise.
If you're going to be using a sproc, you could try this approach:
IF (#Field2 = 'SomeWildcardValue')
SELECT Field1, Field2, Field3 FROM SomeTable
ELSE
SELECT Field1, Field2, Field3 FROM SomeTable WHERE Field2 = #Field2
Or even, keep them completely separate, create one sproc to return ALL, and one to return based on Field2 search. Although if you have a larger number of parameters, this can soon mount up!
In general I would use the nullable parameter method in an SP. Based on your code though, why don't you build the SQL commandtext based on what the user has entered? For example, only add a where clause to the SQL if the user has entered something into the text box, otherwise, just a simple select.

Categories