Multiple conditional parameters in SQL and C# - c#

Consider the following function which has 2 optional variables
public List<T> SelectSqlItems<T>(
string settingsgroup = null,
int? state = null)
{
SqlCommand selectCommand = null;
if (settingsgroup == null)
{
selectCommand = new SqlCommand(
"select * from ApplicationSettings ", con);
}
else
{
selectCommand = new SqlCommand(
string.Format(
"select * from ApplicationSettings where settingsgroup='{0}' ",
settingsgroup),
con);
}
if (state != null)
{
selectCommand.CommandText +=
!selectCommand
.CommandText
.ToLower()
.Contains("where")
? string.Format("where state={0}", state)
: string.Format("and state={0}", state);
}
//etc..
}
I have 4 possibilities:
settingsgroup==null && state==null
settingsgroup==null && state!=null
settingsgroup!=null && state==null
settingsgroup!=null && state!=null
From every case above a different SQL command has to be produced. What are the built in functionalities in C# that could help me achieve such things without a lot of conditional statements, and if you were to write the above how would you write it other than having to overload the function 4 times?

This is a common problem in SQL that can be effectively handled in the query itself, thus allowing queries to be created in advance, use parameters, and be accessed through stored procedures.
Use of parameters is an important recommendation and should not be considered optional. SQL Parameters will help prevent SQL injection attacks. For example, imagine if someone were to call your method using the following parameter values:
SelectSqlItems<T>("' OR settingsgroup <> '", null);
Your query would now become:
select * from ApplicationSettings where settingsgroup='' OR settingsgroup<>''
This would of course return all rows from the table, and potentially expose private information. Even worse possibilities exist, however, such as inserting a DELETE clause which could delete your whole table, or even drop your entire database (though hopefully your user permissions are configured to at least prevent these worst-case scenarios).
To prevent this, your SelectSqlItems method can be restated to the following:
public List<T> SelectSqlItems<T>(
string settingsgroup = null,
int? state = null)
{
var cmdText = "..."; // See Query Below
var selectCommand = new SqlCommand(cmdText, con);
// Set the values of the parameters
selectCommand.Parameters.AddWithValue("#settingsgroup", settingsgroup);
selectCommand.Parameters.AddWithValue("#state", state);
// etc...
}
Your query can now be stated as follows:
SELECT
*
FROM
ApplicationSettings
WHERE
((#settingsgroup IS NULL) OR (settingsgroup=#settingsgroup))
AND
((#state IS NULL) OR (state=#state))
If a parameter value is null, the left side of the conditional statement joined by OR will always have the value TRUE, and therefore all rows will be matched. If, however, the parameter value is not NULL, the left side of the conditional will have the value FALSE and the right side will be inspected. The right side will only have the value TRUE if the row's value matches the parameter value, and therefore only the rows matching the parameter value will be returned. This concept can be repeated with as many parameters as required.

Why not switch to an SQL stored procedure with both parameters being optional and pass the parameters passed to SelectSqlItems directly to it ?

If you switched to a ORM solution like Entity Framework you could dynamically build your query with functions easily.
public List<T> SelectSqlItems<T>(string settingsgroup=null,int? state=null)
{
using(var context = new MyContext())
{
IQueyable<ApplicationSettings> query = context.ApplicationSettings;
if(settingsgroup != null)
query = query.Where(row => row.settingsgroup = settingsgroup);
if(state != null)
query = query.Where(row => row.state = state.Value)
Expression<Func<ApplicationSettings, T>> selectExpression = GetSelectExpression<T>();
return query.Select(selectExpression).ToList();
}
}

This would probably work. It also won't enforce null if a parameter is not passed in. You should look into using Parameters if you are concerned about injection attacks. This is not a safe way to add parameters to a query.
string stateCompare = state.HasValue ? "state = " + state.Value : "";
string settingsgroupCompare = String.IsNullOrEmpty(settingsgroup) ? "IS NULL" : "= " + settingsgroup;
string whereCondition = !String.IsNullOrEmpty(stateCompare) || !String.IsNullOrEmpty(settingsgroupCompare)?"WHERE":"";
SqlCommand selectCommand = new SqlCommand(String.Format("select * from ApplicationSettings {0} {1} {2}",whereCondition, settingsgroupCompare, stateCompare);

Mandatory SQL injection warning: Do not use string constants originating in the user's input directly. Parameterize your queries.
If you insist on building a SQL statement dynamically (as opposed to having it built by one of the built-in or open-source ORM solutions available in .NET), you could either simplify your code by using a fake WHERE 1=1 condition, a common trick of dynamic SQL builders, or by "encoding" the states as numbers, and processing them in a switch.
The "trick" solution starts like this:
if (settingsgroup == null) {
selectCommand = new SqlCommand("select * from ApplicationSettings WHERE 1=1 ", con);
} else {
selectCommand = new SqlCommand(string.Format("select * from ApplicationSettings where settingsgroup='{0}' ", settingsgroup), con);
}
This looks similar to what you have, except that you no longer need to check the existing string for presence or absence of the WHERE clause: it's always there. You can continue with your simplified code:
if (state != null) {
selectCommand.CommandText += string.Format("and state={0}",state);
} ... // and so on
An alternative would be "encoding" the state explicitly in a state number, and using it in a switch, like this:
int conditionForm = 0;
if (settingsgroup != 0) conditionForm |= 1;
if (state != 0) conditionForm |= 2; // Use powers of two
Now the conditionForm variable can have one of four values from the range 0..3, inclusive. You can write a switch statement that deals with each condition separately:
switch (conditionForm) {
case 0:
selectCommand = new SqlCommand("select * from ApplicationSettings", con);
break;
case 1:
selectCommand = new SqlCommand(string.Format("select * from ApplicationSettings where settingsgroup='{0}'", settingsgroup), con);
break;
case 2:
selectCommand = new SqlCommand(string.Format("select * from ApplicationSettings where state='{0}'", state), con);
break;
case 3:
selectCommand = new SqlCommand(string.Format("select * from ApplicationSettings where settingsgroup='{0}' and state='{1}'", settingsgroup, state), con);
break;
}

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)

Fatal error encountered during command execution while updating blob to mysql

String query = "";
string constr = ConfigurationSettings.AppSettings["MySQLConnectionStringForIMS"];
using (MySqlConnection con = new MySqlConnection(constr))
{
//string query = "INSERT INTO user(name, files,contentType) VALUES (#name,#files,#contentType)";
if (update == "mainSec")
{
query = "update main_section set contentType=#contentType,fileData=#fileData,fileNameAfterUploading=#fname,haveDir=#dir where id=#id";
}
else
{
query = "update sub_section set subContentType=#contentType,subFileData=#fileData,fileNameAfterUploading=#fname,haveDir=#dir where MainSecId=#id and id=#subId";
}
using (MySqlCommand cmd = new MySqlCommand(query))
{
cmd.Connection = con;
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("#contentType", contentType);
cmd.Parameters.AddWithValue("#fileData", data);
cmd.Parameters.AddWithValue("#fname", filename);
cmd.Parameters.AddWithValue("#dir", 1);
cmd.Parameters.AddWithValue("#id", mainId);
if (update == "subSec")
{
cmd.Parameters.AddWithValue("#subId", subId);
}
con.Open();
int st = cmd.ExecuteNonQuery();
if (st == 1)
{
//Uri uri = new Uri(url, UriKind.Absolute);
//System.IO.File.Delete(uri.LocalPath);
}
con.Close();
}
}
We are using MySql.Data.dll version 6.9.5.0.
This fails with the error: mysql Fatal error encountered during command execution. Any ideas on why this would fail?
Tl;DR
Because of mismatched branch comparisons, you are executing a query with 6 unbound variables, but you are only binding 5 parameters.
Detail
There wasn't really sufficient information provided in the stack trace / exception to answer definitively, but it seems the guess about the bad practice in the branching above was the root cause, i.e. in these two branches:
if (update == "mainSec")
{
query = ... Query has 5 unbound variables
}
else
{
query = ... Query has 6 unbound variables
}
and
if (update == "subSec")
{
... bind the 6th parameter here
}
.. because the update type / mode string wasn't constrained to a range of mainSec or subSec, there is a branch which used the sub_section query with 6 parameter tokens, but which didn't bind the 6th token, causing the error.
In situations like this, I would recommend that instead of using weakly constrained strings, that you rigidly constrain the range of inputs of your update, e.g. with an enum:
enum UpdateMode
{
Invalid = 0, // This will be the default, and can be used to ensure assignment
MainSection,
SubSection
}
Since there's only two possible modes, you could avoid the first query assignment branch with a conditional assignment, i.e.
Contract.Assert(updateMode != UpdateMode.Invalid);
var query = updateMode == UpdateMode.MainSection
? "update main_section set contentType=#contentType ... "
: "update sub_section set subContentType=#contentType ... ";
This has the benefits that the declaration and assignment of query can be tied together (and provides additional compiler guarantees that query must be assigned).
(And if there were more than two queries (and more than two enum states) then a static IReadOnlyDictionary<enum, string> would allow this pattern to be extended.)
The binding would also change to
if (updateMode == UpdateMode.SubSection)
{
cmd.Parameters.AddWithValue("#subId", subId);
}
Some notes
con.Close(); isn't needed, since you already have a using around the new Connection - Dispose will call .Close if it's open
I know this is commented out, but I would strongly recommend against doing File IO at this point
if (st == 1)
{
// File.IO
}
Since
From a separation of concerns point of view, deleting files belongs elsewhere. If the deletion is dependent on exactly one row being updated, then this can be returned from this Blob update method.
The I/O would be in the scope of the using block, this will potentially hold up the release of a MySql Connection (to the connection pool)
The IO could fail, and depending on any transaction control, this could leave your system in a problematic state, where the record is deleted but the file is not.

IndexOutOfRange Exception in sqldatareader using c#

I create an application using c# , In my authentification interface , i have a test control , i want to know profile user .
My database contains table named user which contains 4 columns
(id_user,name ,mail, profile)
Here is my code
public string profil_user(string login)
{
SqlConnection conn = new database().connect_user();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "select profile from user where name = '" + login + "';";
SqlDataReader s = cmd.ExecuteReader();
if (s.Read())
{
return ( s.GetString(3));
}
else{return ("false"); }
}
but i have an exception in s.GetString(3)
system.IndexOutOfRange : index was outside the bounds of the array
You're only selecting a single field (profile) but then you're trying to select the 4th field (index 3) here:
return ( s.GetString(3));
In addition to just returning s.GetString(0) I would strongly advise you to:
Use parameterized SQL - always do this, to prevent SQL injection attacks, make your code more readable, and prevent unexpected text conversion problems
Either throw an exception or return null if the profile isn't found, instead of returning the string "false"
Use using statements for disposable things like SqlCommand, SqlConnection and SqlDataReader to ensure that you clean up resources appropriately
Start following .NET naming conventions to make your code more idiomatic
So something like:
public string GetUserProfile(string login)
{
string sql = select profile from user where name = #login";
// I assume Connect() returns an *open* connection?
using (var conn = new Database().Connect())
{
using (var command = new SqlCommand(sql, conn))
{
command.Parameters.Add("#login", SqlDbType.NVarChar).Value = login;
using (var reader = command.ExecuteReader())
{
// If it's an error (code failure) for there to be no matching profile,
// you may want to throw an exception instead.
return s.Read() ? s.GetString(0) : null;
}
}
}
}
So you want the fourth row, not the fourth column which you try to access with s.GetString(3):
int rowNum = 0;
while(s.Read())
{
if(++rowNum == 4)
{
return s.GetString(0);
}
}
return "false";
However, it is a bit strange to access the fourth row when you don't use an Order By. You should also only return the row that you want with the correct sql query.
You are also open for sql injection if you use string concatenation here:
cmd.CommandText = "select profile from user where name = '" + login + "';";
Use sql parameters:
cmd.CommandText = "select profile from user where name = #login";
cmd.Parameters.Add("#login", SqlDbType.VarChar).Value = login;
have 4 columns not rows
Ok, so you instead want the fourth column. Why don't you use the name instead?
Since you only select the profile-column(the fourth), you could simply use GetString(0). But you could also select all columns and then determine the correct index with GetOrdinal:
int profileColumnIndex = s.GetOrdinal("profile");
return s.GetString(profileColumnIndex);
This is useful if you don't control the query or it might be changed in future.
You are selecting only 1 field, thus index 3 is out of bounds. It also very important to Use parameters. Try:
cmd.CommandText = "select profile from user where name = #login;";
cmd.Parameters.Add("#login, SqlDbType.NVarChar).Value = login;
SqlDataReader s = cmd.ExecuteReader();
while (s.Read())
{
return s[0].ToString();
}
The parameter for SqlDataReader.GetString should be the column index. You're only selecting one column so you get an exception.
Because you do not have all the fields in your select list
Change the SQL to:
select id_user,name ,mail, profile from user where name = '" + login + "';

Build sql query with string builder and a lot of parameters

I am building my query using loops
for (var i = 0; i < query.BlackWhiteListFieldMatchProxy.Count; i++)
{
sb.Append($#"(
MatchType = {(int)query.BlackWhiteListFieldMatchProxy[i].MatchType}
AND EntityType = {(int)query.BlackWhiteListFieldMatchProxy[i].EntityType}
AND MatchFieldType = {(int)query.BlackWhiteListFieldMatchProxy[i].MatchFieldType}
AND (
(
MatchValue = '{query.BlackWhiteListFieldMatchProxy[i].MatchValue1}'
OR MatchValue = {query.BlackWhiteListFieldMatchProxy[i].MatchValue2Or ?? "Null"}
)
AND
(
{query.BlackWhiteListFieldMatchProxy[i].MatchValue2And ?? "Null"} is Null
OR MatchValue2 is Null
OR MatchValue2 = {query.BlackWhiteListFieldMatchProxy[i].MatchValue2And ?? "Null"}
)
)
)");
if (i != query.BlackWhiteListFieldMatchProxy.Count - 1)
sb.Append($#"
OR");
}
But I have the problem with this
{query.BlackWhiteListFieldMatchProxy[i].MatchValue2And ?? "Null"} is Null
If it Null It'll work, otherwise the real value will be without ""
The thing is I cant use something like
#MatchValue
Because I have the list of parameters with the same name which differs only by it's number in the List the names will be the same and It won't map it properly
for (var i = 0; i < query.BlackWhiteListFieldMatchProxy.Count; i++)
{
var match = query.BlackWhiteListFieldMatchProxy[i];
cmd.Parameters.AddWithValue($"#matchType{i}", (int)match.MatchType);
cmd.Parameters.AddWithValue($"#entityType{i}", (int)match.EntityType);
cmd.Parameters.AddWithValue($"#fieldType{i}", (int)match.MatchFieldType);
sb.Append($#"(
MatchType = #matchType{i}
AND EntityType = #entityType{i}
AND MatchFieldType = #fieldType{i}
... etc
Add additional parameters per required element - so there will be #matchType0, #matchType1, etc - then you have no injection vulnerabilities. One thing to watch: parameters with a value of null are not sent, so check for null and either generate different SQL, or be sure to set the parameter value to DBNull.Value in that case.
First of all this is terrible way of structuring SQL. This SQL has many issues. First and most important thing is it is open to SQL Injection Attacks. But, It has also other performance related issues where SQL Query is changing whenever different value is provided.
I Suggest you to use parameterized SQL. here is the most basic example for it.
Please note that code may be subject to change according to the library that you are going to use.
// 1. declare command object with parameter #City
SqlCommand cmd = new SqlCommand(
"select * from Customers where city = #City", conn);
// 2. define parameters used in command object
SqlParameter param = new SqlParameter();
param.ParameterName = "#City";
param.Value = inputCity;
if we turn back to your case. The Final Code would be like this:
"... SQL ...
MatchType = #matchType AND
... SQL ..."
The code needs to be like
cmd.Parameters.AddWithValue("#matchType", (int)match.MatchType);
and for the null values you may consider to use
DbNull.Value

How to return the value of query from function?

I have crated one function to return the result of executed SQL query as follows:
EDITED :
public int GetChips(int points, string username)
{
int chip = 0;
string getChips = "SELECT Chips from tbl_UserInfo where UserName =' " + username + " '";
con = new MySqlConnection(conString);
con.Open();
MySqlCommand cmd = new MySqlCommand(getChips, con);
MySqlDataReader chips = cmd.ExecuteReader();
while (chips.Read())
{
chip = chips.GetInt32(0);
if (chip > points)
{
if (points == 5000)
{
chip = chip - 5000;
}
else if (points == 10000)
{
chip = chip - 10000;
}
}
}
con.Close();
return chip;
}
It returns chip's value as 0. This code does not go in 'while' condition.
What can be the problem ?
How can I solve this ?
Well yes... temp is a MySqlDataReader, not an int. You can't assign a MySqlDataReader to a variable of type int.
I suspect you want:
chip = temp.GetInt32(0);
Note that you should use using statements to release all your resources, rather than just closing the connection explicitly. You should also consider what you want to happen if there's more than one result (or no results).
Additionally, your code will fail at execution time at the moment unless the user puts their username in quotes. Don't fix this by adding quotes to the SQL - use parameterized queries instead. Otherwise you're vulnerable to SQL injection attacks. Basically, you should pretty much never build SQL dynamically using values like this - always parameterize them instead.
An answer to your edited question:
Are you sure the select statement is returning any values at all? You have a space inside your ' " and " '" so it will look for ' Rohan ' in stead of 'Rohan'. Try
UserName ='" + username + "'"
Or better yet, like Jon suggested, use parameterized queries!
temp is a MySqlDataReader whereas chip is an int. Of course your can't assign one to the other.
Because your query just returns a value, so you can use this:
chip = (int)(command.ExecuteScalar() ?? 0);
Have a look at following link for more detail:
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.executescalar.aspx

Categories