Build sql query with string builder and a lot of parameters - c#

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

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)

Get output of the updated row only if the updated row is not null?

I am having issues when trying to get the result of an updated row, the query that I use as shown below update a certain row when it meets a specific conditions,however, and though the conditions are not met, the Update query output gets the value "0" rather than NULL, Is there any tips so that I can fix this in the same query,with that said, I'm trying to avoid the use of other queries because it's within a method that has to optimised the most possible.
SqlCommand loadbonus = new SqlCommand();
loadbonus.Connection = conn;
loadbonus.CommandText = "UPDATE Client SET [Restant Bonus]=[Restant Bonus]-(SELECT SUM([Prix Total]) FROM Comptoir WHERE [N° Comptoir]='" + tabcontrol.SelectedIndex + "'),[Total Consommé]=[Total Consommé]+(SELECT SUM([Prix Total]) FROM Comptoir WHERE [N° Comptoir]='" + tabcontrol.SelectedIndex + "') OUTPUT INSERTED.[Restant Bonus] WHERE [Code Client]=#CodeClient AND [Bonus Fidélité]='1'";
loadbonus.Parameters.Add("#CodeClient", SqlDbType.Int).Value = string.IsNullOrWhiteSpace(clientcodecomptoire.Text) ? DBNull.Value : (object)Convert.ToInt32(clientcodecomptoire.Text);
int restantbonus = Convert.ToInt32(loadbonus.ExecuteScalar());
if (restantbonus <= 0)
{// here is the issue! the left side of the If statement can containt there "0" value for both a "0" value and a Null value}
Using a stored procedure has many benefits. It starts to get you a separate data layer from the application. It is also parameterized so you can avoid sql injection.
Here is what your procedure might look like.
create procedure Client_Update
(
#NComptoir int
) as
set nocount on;
declare #MyTotal int;
SELECT #MyTotal = SUM([Prix Total])
FROM Comptoir
WHERE [N° Comptoir] = #NComptoir;
UPDATE Client
SET [Restant Bonus] = [Restant Bonus] - #MyTotal
,[Total Consommé] = [Total Consommé] + #MyTotal;
select #MyTotal;
Then to use in dotnet you should change up a few things. You need to use the USING statement around objects like commands and connections to ensure they get properly disposed of. You already have a connection object so I am leaving that to you.
using (SqlCommand loadbonus = new SqlCommand("Client_Update", conn))
{
loadbonus.CommandType = CommandType.StoredProcedure;
loadbonus.Parameters.Add("#NComptoir", SqlDbType.Int).Value = tabcontrol.SelectedIndex;
int restantbonus = Convert.ToInt32(loadbonus.ExecuteScalar());
if (restantbonus <= 0)
{
}
}
The issue I see though is that you add a parameter (#Seuil) in your code but it is not part of the update statement. And since you are setting the value to the text of a textbox it will get 0 when set to an empty string. That is because it is being converted to an int. I have no idea what the purpose of that parameter is though because it is not part of your query.
I hope this helps at least give you a push in the right direction.

Multiple conditional parameters in SQL and 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;
}

SQL Parameters and Question Marks

I am making the switch from classic ASP to ASP.NET. And I am having some trouble doing some basic stuff that I used to do easily with the old method. Below is a handy ASP function that I used to execute scalar queries with a few lines.
FUNCTION ExecuteScalarParams(SQLQuery, Parameter_Array)
Set cmd1 = Server.CreateObject("ADODB.Command")
cmd1.ActiveConnection = con
cmd1.CommandText = SQLQuery
cmd1.CommandType = 1
FOR ParamCount = 0 TO UBOUND(Parameter_Array)
cmd1.Parameters(ParamCount) = Parameter_Array(ParamCount)
NEXT 'ParamCount
Set rstScalar = cmd1.Execute()
IF NOT rstScalar.EOF THEN
arrScalar = rstScalar.GetRows()
IF UBOUND(arrScalar,2) = 0 THEN
ExecuteScalarParams = arrScalar(0,0)
ELSE
ExecuteScalarParams = NULL
END IF
ELSE
ExecuteScalarParams = NULL
END IF
rstScalar.Close
Set rstScalar = Nothing
Set cmd1 = Nothing
END FUNCTION
I used to pass a SQL query with question marks as place holders for the parameters like this:
SELECT TOP 1 UserName FROM Members WHERE (Created>?) AND (AdminLevel=?);
I would then set up a parameters array and pass it on to the function:
MyArray = ARRAY("1-JAN-2012",1)
The parameters in the array would replace the question marks in the query string in the order they appear.
I am trying to mimic this function in C# but I am stuck in the part where I have to pass the parameters. So far I got to the point where I have to used named place holders such as #Created and #AdminLevel instead of the question marks and then I have to set up parameter objects like this:
SqlParameter param = new SqlParameter();
param.ParameterName = "#AdminLevel";
param.Value = 1;
Is there a way to pass the parameters without having to set the parameter names and simply use question marks and the order in which they appear to specify which parameter goes where?
edit: as pointed out by Dana the MSDN Docs for Parameters shows you need to use named parameters for SqlClient but can use positional parameters for OleDb/ODBC.
You can make adding parameters a lot easier by using the code below; it's the skeleton I use but I'm sure there's a better way of doing it.
You still need to used named parameters, but you can simulate your question marks to an extent by naming them #a, #b, #c.. - positional parameters are fine until you get more than a handful of parameters and you have to constantly count the number of question marks to figure out which parameter value is being applied where, often resulting in mistakes.
using (var con = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString))
{
con.Open();
{
using (var command = con.CreateCommand())
{
command.Connection = conn;
command.CommandText = "SELECT * FROM [dbo].[Table] WHERE [c1] = #a AND [c2] = #b";
command.Parameters.AddWithValue("#a", aVal);
command.Parameters.AddWithValue("#b", bVal);
command.CommandType = CommandType.Text;
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
///
}
}
else
{
///
}
}
}
}
}

How to build a parameterised query with IN sql keyword?

That's what I tried & failed:
string sql = "... WHERE [personID] IN (#sqlIn) ...";
string sqlIn = "1,2,3,4,5";
SqlCeCommand cmd.Parameters.Add("#sqlIn", SqlDbType.NText).Value = sqlIn;
SqlCeDataAdapter da = new SqlCeDataAdapter(cmd);
da.Fill(ds); // > Error
Error details:
The ntext and image data types cannot be used in WHERE, HAVING, GROUP BY, ON, or IN clauses, except when these data types are used with the LIKE or IS NULL predicates.
Can't I pass all the IDs as one parameter? Should I add one by one all IDs?
P.S: Notice SqlCE
You can't parameterise that as a single parameter. Your query is doing an "in" on a single value, so is essentially:
... Where personId = '1,2,3,4,5'
(give or take a parameter). This is usually also an invalid or sub-optimal equality test, and certainly isn't what you were trying to query.
Options;
use raw concatenation: often involves a SQL injection risk, and allows poor query plan re-use
on full SQL server: use a UDF to split a single param
on full SQL server, use a TVP
add parameters dynamically, and add the various #param3 etc to the TSQL
The last is the most reliable, and "dapper-dot-net" has a feature built in to do this for you (since it is commonly needed):
int[] ids = ...
var rows = conn.Query<SomeType>(
#"... Where Id in #ids",
new { ids }).ToList();
This, when run via dapper-dot-net, will add a parameter per item in "ids", giving it the right value etc, and fixing the SQL so it executes, for example:
"... Where Id in (#ids0, #ids1, #ids2)"
(if there were 3 items in "ids")
You'll need to split the sqlIn string by comma, convert each to an integer, and build the IN statement manually.
string sqlIn = "1,2,3,4,5";
string inParams = sqlIn.Split(',');
List<string> paramNames = new List<string>();
for(var i = 0; i < inParams.Length; ++i){
string paramName = "#param" + i.ToString();
SqlCeCommand cmd.Parameters.Add(paramName, SqlDbType.Int).Value = int.Parse(inParams[i]);
paramNames.Add(paramName);
}
string sql = "... WHERE [personID] IN (" +
string.Join(",", paramNames) +
") ...";

Categories