.NET: How to deal with possible null values in an SQL query? - c#

Code:
private void DoSomethingWithDatabase(string f1, int f2)
{
SqlCommand myCommand = new SqlCommand("SELECT Field1,Field2,Field3 FROM MyTable WHERE Field1 = #F1 AND Field2 = #F2", this.myConn);
myCommand.Parameters.Add("#F1", System.Data.SqlDbType.VarChar);
myCommand.Parameters.Add("#F2", System.Data.SqlDbType.Int);
if (f1 == "")
myCommand.Parameters["#F1"].Value = DBNull.Value;
else
myCommand.Parameters["#F1"].Value = f1;
if (f2 < 0)
myCommand.Parameters["#F2"].Value = DBNull.Value;
else
myCommand.Parameters["#F2"].Value = f2;
// code to do stuff with the results here
}
The server is a Microsoft SQL Server instance.
The database table MyTable contains fields which are nullable. Therefore null is a valid value to search on when performing the query.
From my reading, and also from testing code like this, doing something like what I did here doesn't work properly because apparently you can't do an "equals null" comparison this way - you're supposed to do "IS NULL".
It looks like you can correct this and make it work by setting ANSI_NULL to OFF (as per https://msdn.microsoft.com/en-us/library/ms188048.aspx) but it also indicates this method is deprecated and should not be used.
That article suggests you can use an OR operator to do something like WHERE Field1 = 25 OR Field1 IS NULL. The problem is, with a single call to this function, I want to check for either null and only null, or for the given constant value and nothing else.
So far, it seems like I need to basically build the query piece by piece, string by string, to account for the possibility of NULL values. So I'd need to do something like:
string theQuery = "SELECT Field1,Field2,Field3 FROM MyTable WHERE ";
if (f1 == "")
theQuery += "Field1 IS NULL ";
else
theQuery += "Field1 = #F1 ";
// ...
SqlCommand myCommand = new SqlCommand(theQuery, this.myConn);
if (f1 == "")
{
myCommand.Parameters.Add("#F1", System.Data.SqlDbType.VarChar);
myCommand.Parameters["#F1"].Value = f1;
}
// ...
Is this really how it needs to be done? Is there a more efficient way to do this without repeating that if block and by using parameters rather than concatenating a query string together?
(Notes: An empty string is converted to a NULL here for the example. In the scenario I'm actually working with, empty strings are never used, but instead NULL is stored. The database is out of my control, so I can't just say "change all your NULLs to empty strings". Same goes for the ints - if we pass, say, -1 into the function it should be testing for a null value. Bad practice? Maybe, but the database itself is not in my control, only the code to access it is.)

Why not using:
string theQuery = "SELECT Field1, Field2, Field3 FROM MyTable WHERE ISNULL(Field1,'') = #F1";
?
That way you get rid of your if block and your null values are interpreted as an empty string, like your f1 variable.

SQL Server has a function called ISNULL(Column,Value) where you can specify one column to check and set a Default Value in case this Column is NULL.
You can check here

You could do something like
WHERE ISNULL(Field, '') = #F1
In that case, NULL fields are treated like empty strings. Another way would be:
WHERE Field IS NULL OR Field = #1

The way you are dealing with NULLs is the right way. Maybe you could use a helper method like this one:
private string AppendParameter(string query, string parameterName, string parameterValue, SqlParameterCollection parameters)
{
if (string.IsNullOrEmpty(parameterValue))
query += "Field1 IS NULL ";
else
{
query += "Field1 = " + parameterName + " ";
parameters.AddWithValue(parameterName, parameterValue);
}
return query;
}

SqlCommand myCommand = new SqlCommand(#"SELECT Field1,Field2,Field3
FROM MyTable
WHERE
( (#F1 IS NULL AND [field1] IS NULL) OR [field1] = #F1 ) AND
( (#F2 IS NULL AND [field2] IS NULL) OR [field2] = #F2 );", this.myconn);
myCommand.Parameters.Add("#F1", System.Data.SqlDbType.VarChar).Value = DBNull.Value;
myCommand.Parameters.Add("#F2", System.Data.SqlDbType.Int).Value = DBNull.Value;
if (!string.IsNullOrEmpty(f1))
myCommand.Parameters["#F1"].Value = f1;
if (f2 != -1)
myCommand.Parameters["#F2"].Value = f2;
This would utilize indexes on fields.

If you want to avoid if blocks, you can probably change the query into something like this:
SELECT Field1,Field2,Field3 FROM MyTable
WHERE COALESCE(Field1,'') = #F1 AND COALESCE(Field2,-1) = #F2

Related

Object cannot be cast from DBNull to other types in C#

I wrote this code :
MySqlCommand command1 = new MySqlCommand("SELECT SUM(standard_one) FROM `challenge` WHERE (SELECT DAYOFWEEK(date)=1) AND challenge_id = #challenge_id and username = #username", db.getConnection());
command1.Parameters.Add("#challenge_id", MySqlDbType.Int32).Value = comboBox1.SelectedItem;
command1.Parameters.Add("#username", MySqlDbType.VarChar).Value = globals.username;
the problem is some times this command returns null.
how can I check if the command will return null? and if so it returns 0?
if the command will return null? and if so it returns 0?
Make the SQL:
SELECT COALESCE(SUM(standard_one), 0) ...
Bear in mind that you then won't be able to tell the difference between "there were no values that matched the where clause" and "the sum of all values that matched was 0".. If that's important, you'll either need to coalesce to a different value that would never occur in the sum, like -2_147_483_648 or persist with what you have and check for null on the C# side
var x = command1.ExecuteScalar();
if(x == null || x.GetType() == typeof(System.DBNull))
...
else
..cast to int etc..

IF condition check inside USING method and SqlConnection

I am trying to run data validation, execute some code and pass data from one SQL query to another.
My current code looks like the below:
public string SelectUniqueKeyNumber()
{
string newList = string.Join(Environment.NewLine, listOfSkus).ToString();
string key_id;
string sqlConnectionString = #"someConnectionString";
using (SqlConnection connection = new SqlConnection(sqlConnectionString))
{
connection.Open();
SqlCommand command = new SqlCommand("select top 1 KEY_NUMBER from MyTable where QTY_ON_HAND > 0 " + newList + " order by NEWID()", connection);
SqlDataReader readerKey = command.ExecuteReader();
readerKey.Read();
key_id = String.Format(readerKey[0].ToString());
}
SelectSkuNumber(key_id);
return key_id;
}
What I am trying to do is to check if my readerKey.Read() is not returning null value. If it does then stop the process, otherwise continue. I've tried it in the way as shown below:
public string SelectUniqueKeyNumber()
{
string newList = string.Join(Environment.NewLine, listOfSkus).ToString();
string key_id;
string sqlConnectionString = #"someConnectionString";
using (SqlConnection connection = new SqlConnection(sqlConnectionString))
{
connection.Open();
SqlCommand command = new SqlCommand("select top 1 KEY_NUMBER from MyTable where QTY_ON_HAND > 0 " + newList + " order by NEWID()", connection);
SqlDataReader readerKey = command.ExecuteReader();
readerKey.Read();
if(readerkey.Read().ToString() == null)
{
//--- stop processing
}
else
{
key_id = String.Format(readerKey[0].ToString());
}
}
SelectSkuNumber(key_id); //---> Then ...(key_id) is not declared value
return key_id;
}
By doing so, I cannot access and pass data of SelectSkuNumber(key_id) due to: Use of unassigned local variable 'key_id'
Any ideas?
All you need do is assign something to key_id when you declare it, like:
string key_id = null; // not string key_id;
and later, after the using:
if (key_id != null)
{
SelectSkuNumber(key_id); //---> Then ...(key_id) is not declared value
}
return key_id;
The caller of the function should, of course, know what to do if a null is returned.
To avoid that particular problem, you can assign some value or nnull to key_id, eg. key_id = "";.
But you have some more problem there:
You are prone to SQL injection, you should use Parameters collection of SqlCommand class.
Are you sure you are concatenating your query correctly? Let's suppose
newList = {"some", "thing"};
Then your query would be:
select top 1 KEY_NUMBER
from MyTable where QTY_ON_HAND > 0
some
thing
order by NEWID()
Which is very, very incorrect to say the least.
if(readerkey.Read().ToString() == null) condition... Read returns bool, which is either true or false, it isn't reference type, so ToString() will never be null, thus the condition will always fail. If you want to check if there was NULL in database you should check:
if (readerKey.Read() && readerKey["KEY_NUMBER"] == DBNull.Value)
which first read row, then receives value of column in that row. It uses short-circuiting for the case, where no records are returned.
readerKey.Read(); is unnecessary before the if statement.

How to convert nullable int to SQL Null value?

I need to perform an update to a View that has multiple underlying tables using the ExecuteCommand method of a DataContext. I am using this method because of the known restriction of linqToSQL when performing this type of operation on Views having multiple underlying tables.
My existing SQL statement is similar to the following where I am setting newFieldID to a null value simply for this post to illustrate the issue. In the application, newFieldID is assigned a passed parameter and could actually be an integer value; but my question is specific to the case where the value being provided is a null type:
using (var _ctx = new MyDataContext())
{
int? newFieldID = null;
var updateCmd = "UPDATE [SomeTable] SET fieldID = " + newFieldID + "
WHERE keyID = " + someKeyID;
try
{
_ctx.ExecuteCommand(updateCmd);
_ctx.SubmitChanges();
}
catch (Exception exc)
{
// Handle the error...
}
}
This code will fail for the obvious reason that the updateCmd won't be completed in the case of a null value for the newFieldID. So how can I replace or translate the CLR null value with an SQL null to complete the statement?
I know I could move all of this to a Stored Procedure but I am looking for an answer to the existing scenario. I've tried experimenting with DBNull.Value but aside from the challenge of substituting it for the newFieldID to use in the statement, simply placing it into the string breaks the validity of the statement.
Also, enclosing it within a single quotes:
var updateCmd = "UPDATE [SomeTable] SET fieldID = '" + DBNull.Value + "'
WHERE keyID = " + someKeyID;
Will complete the statement but the value of the field is translated to an integer 0 instead of an SQL null.
So How does one go about converting a CLR null or nullable int to an SQL Null value given this situation?
Correct way to do it: use override of ExecuteCommand accepting not only command text, but also array of parameters and use parameterized query instead of command string concatenation:
var updateCmd = "UPDATE [SomeTable] SET fieldID = {0} WHERE keyID = {1}";
_ctx.ExecuteCommand(updateCmd, new [] {newFieldID, someKeyID});
It will not only prevent you from sql injection, but also it will do following for you (from MSDN description):
If any one of the parameters is null, it is converted to DBNull.Value.
Try checking newFieldID == null and change the statement accordingly.
Something like below or using separate if / else statement.
var updateCmd = "UPDATE [SomeTable] SET fieldID =" + (newFieldID == null ? "null" : Convert.ToString(newFieldID)) + " WHERE keyID = " + someKeyID;
Normally, when using Stored Procedures or Prepared Statements, you use Parameters to assign values. When you have a DbParameter, you can assign null or DBNull.Value to the Value-Property or your parameter.
If you want to have the null as text in the statement, simply use the SQL-keyword NULL
var updateCmd = "UPDATE [SomeTable] SET fieldID = NULL WHERE keyID = " + someKeyID;
As pointed out by Andy Korneyev and others, a parameterized array approach is the best and probably the more appropriate method when using Prepared statements. Since I am using LinqToSQL, the ExecuteCommand method with the second argument which takes an array of parameters would be advised but it has the following caveats to its usage.
A query parameter cannot be of type System.Nullable`1[System.Int32][] (The main issue I'm trying to resolve in this case)
All parameters must be of the same type
Shankar's answer works although it can quickly become very verbose as the number of parameters could potentially increase.
So my workaround for the problem involve somewhat of a hybrid between the use of parameters as recommended by Andy and Shankar's suggestion by creating a helper method to handle the null values which would take an SQL statement with parameter mappings and the actual parameters.
My helper method is:
private static string PrepareExecuteCommand (string sqlParameterizedCommand, object [] parameterArray)
{
int arrayIndex = 0;
foreach (var obj in parameterArray)
{
if (obj == null || Convert.IsDBNull(obj))
{
sqlParameterizedCommand = sqlParameterizedCommand.Replace("{" + arrayIndex + "}", "NULL");
}
else
sqlParameterizedCommand = sqlParameterizedCommand.Replace("{" + arrayIndex + "}", obj.ToString());
++arrayIndex;
}
return sqlParameterizedCommand;
}
So I can now execute my statement with parameters having potential null values like this:
int? newFieldID = null;
int someKeyID = 12;
var paramCmd = "UPDATE [SomeTable] SET fieldID = {0} WHERE keyID = {1}";
var newCmd = PrepareExecuteCommand(paramCmd, new object[] { newFieldID });
_ctx.ExecuteCommand(newCmd);
_ctx.SubmitChanges();
This alleviates the two previously referenced limitations of the ExecuteCommand with a parameter array. Null values get translated appropriately and the object array may varied .NET types.
I am marking Shankar's proposal as the answer since it sparked this idea but posting my solution because I think it adds a bit more flexibility.

SqlCommand SUM(NVL()) issue

Hello I'm trying to select SUM of all payments but got this exception: nvl is not a recognized function name
with this code:
SqlCommand sc2 = new SqlCommand("SELECT SUM(NVL(payments,0)) AS sumcastka FROM kliplat WHERE akce=" + zakce.Text, spojeni);
spojeni.Open();
int sumOfPrice = 0;
object vysledek2 = sc2.ExecuteScalar();
if (vysledek2 != DBNull.Value)
sumOfPrice = Convert.ToInt32(vysledek2);
// int vysledek2 = Convert.ToInt32(sc2.ExecuteScalar());
spojeni.Close();
This should work as when no records are found for column "payments" I would like to get "0" if possible.
Thank you for reading this.
NVL() is an oracle-specific function. You can use the ANSI COALSECE function to perform the same task. The benefit of COALESCE is that it takes more than two parameters, and picks the first non-null value.
This should work as when no records are found for column "payments"
No, it will only treat NULL values in the payments column as 0.
If no records are found, then ExecuteScalar returns null (not DBNull):
SqlCommand sc2 = new SqlCommand("SELECT SUM(ISNULL(payments,0)) AS sumcastka FROM kliplat WHERE akce=" + zakce.Text, spojeni);
spojeni.Open();
int sumOfPrice = 0;
object vysledek2 = sc2.ExecuteScalar();
if (vysledek2 != null && vysledek2 != DBNull.Value)
sumOfPrice = Convert.ToInt32(vysledek2);
spojeni.Close();
You should also look into using SqlParameters instead of concatenating strings, but that's a separate issue.
In SQL server there is a funcation called ISNULL for the purpose. Please find the query below:
SELECT SUM(ISNULL(payments,0)) AS sumcastka FROM kliplat WHERE akce=" + zakce.Text, spojeni

Parameterized query which sends NULL value, with C# and SQL Server

I tried to pass some variables into a parameterized query but when the value is NULL it throws an exception that says the value is not provided.
How can I fix that without an if or something like that?
My code is this
var cmdPersona_Log = new SqlCommand();
cmdPersona_Log.Parameters.Clear();
cmdPersona_Log.Connection = mySqlConnection;
cmdPersona_Log.CommandType = CommandType.Text;
cmdPersona_Log.CommandText = #"INSERT INTO [Tomin].[TominRH].[Persona_Log] "
+ "([Id_Action],[Id_User],[Id_Date],[Id_Entidad],[Nombre],[Paterno],[Materno],[Sexo],[Id_Nacionalidad])"
+ " Values (1, 'Admin', #fecha, #id_entidad, #nombre, #paterno, #materno, 1, 52)";
cmdPersona_Log.Parameters.Add("#fecha", DateTime.Now);
cmdPersona_Log.Parameters.Add("#id_entidad", dbRow["CUENTA"]);
cmdPersona_Log.Parameters.Add("#nombre", nombre);
cmdPersona_Log.Parameters.Add("#paterno", paterno);
cmdPersona_Log.Parameters.Add("#materno", materno);
cmdPersona_Log.ExecuteNonQuery();
I will assume that your problem is that the underlying field doesn't support nulls. If this is the case, you could do something like this
cmdPersona_Log.Parameters.Add("#nombre", nombre ?? string.Empty);
cmdPersona_Log.Parameters.Add("#paterno", paterno ?? string.Empty);
cmdPersona_Log.Parameters.Add("#materno", materno ?? string.Empty);
You'll be inserting an empty string in case you have a null using the null-coalescing operator.

Categories

Resources