I'm trying to build a method that queries a SQL table and assigns the values it finds to a new list of objects. Here's a quick example of how it works (assume the reader and connection are set up and working properly):
List<MyObject> results = new List<MyObject>();
int oProductID = reader.GetOrdinal("ProductID");
int oProductName = reader.GetOrdinal("ProductName");
while (reader.Read())
{
results.Add(new MyProduct() {
ProductID = reader.GetInt32(oProductID),
ProductName = reader.GetString(oProductName)
});
}
There are about 40 other properties too, all of them nullable in the MyObject definition, so I'm trying to keep the assignments as tidy as possible. The problem is that I need to assign null values to the object wherever the reader returns a null. In the above code, if the reader throws a "Data is Null" exception. I'm aware it's possible to use an if statement to check for a DbNull first, but since there are so many properties I'm hoping to keep the code cleaner by not having to spell out an if statement for every single property.
A bit of searching led me to the null-coalescing operator, which seems like it should do exactly what I want. So I tried changing the assignments to look like this:
ProductID = reader.GetInt32(oProductID) ?? null,
ProductName = reader.GetString(oProductName) ?? null
Which works fine for any string but gives me errors of Operator '??' cannot be applied to operands of type 'int' and '<null>' (or any other data type except string. I specifically called out the int (and everything else) as nullable in the object definition, but here it's telling me it can't do that.
The Question
Is there a way to handle nulls in this case that can: (1) Be written clearly in-line (to avoid separate if statements for each property), and (2) Work with any data type?
Null from a database is not "null", it's DbNull.Value. ?? and ?. operators won't work in this case. GetInt32, etc. will throw an exception if the value is null in the DB. I do a generic method and keep it simple:
T SafeDBReader<T>(SqlReader reader, string columnName)
{
object o = reader[columnName];
if (o == DBNull.Value)
{
// need to decide what behavior you want here
}
return (T)o;
}
If your DB has nullable ints for example, you can't read those into an int unless you want to default to 0 or something like. For nullable types, you can just return null or default(T).
Shannon's solution is both overly complicated and will be a performance issue (lots of over the top reflection) IMO.
You can write a series of extensions method for each of the standard GetXXXX. These extensions receive an extra parameter that is the default to return in case the value of the field is null.
public static class SqlDataReaderExtensions
{
public int GetInt32(this SqlDataReader reader, int ordinal, int defValue = default(int))
{
return (reader.IsDBNull(ordinal) ? defValue : reader.GetInt32(ordinal);
}
public string GetString(this SqlDataReader reader, int ordinal, int defValue = "")
{
return (reader.IsDBNull(ordinal) ? defValue : reader.GetString(ordinal);
}
public int GetDecimal(this SqlDataReader reader, int ordinal, decimal defValue = default(decimal))
{
....
}
}
This allows you to leave your current code as is without changes or just change the fields that needs the null as return
while (reader.Read())
{
results.Add(new MyProduct() {
ProductID = reader.GetInt32(oProductID),
ProductName = reader.GetString(oProductName, "(No name)"),
MinReorder = reader.GetInt32(oReorder, null)
.....
});
}
You can also have a version where you pass the column name instead of the ordinal position and do the search for the position inside the extension, but this is probably not good from a performance point of view.
Here's an example that works for fields (can easily be converted to properties) and allows for null checks. It does the dreaded if (in a switch), but it's pretty fast.
public static object[] sql_Reader_To_Type(Type t, SqlDataReader r)
{
List<object> ret = new List<object>();
while (r.Read())
{
FieldInfo[] f = t.GetFields();
object o = Activator.CreateInstance(t);
for (int i = 0; i < f.Length; i++)
{
string thisType = f[i].FieldType.ToString();
switch (thisType)
{
case "System.String":
f[i].SetValue(o, Convert.ToString(r[f[i].Name]));
break;
case "System.Int16":
f[i].SetValue(o, Convert.ToInt16(r[f[i].Name]));
break;
case "System.Int32":
f[i].SetValue(o, Convert.ToInt32(r[f[i].Name]));
break;
case "System.Int64":
f[i].SetValue(o, Convert.ToInt64(r[f[i].Name]));
break;
case "System.Double":
double th;
if (r[f[i].Name] == null)
{
th = 0;
}
else
{
if (r[f[i].Name].GetType() == typeof(DBNull))
{
th = 0;
}
else
{
th = Convert.ToDouble(r[f[i].Name]);
}
}
try { f[i].SetValue(o, th); }
catch (Exception e1)
{
throw new Exception("can't convert " + f[i].Name + " to doube - value =" + th);
}
break;
case "System.Boolean":
f[i].SetValue(o, Convert.ToInt32(r[f[i].Name]) == 1 ? true : false);
break;
case "System.DateTime":
f[i].SetValue(o, Convert.ToDateTime(r[f[i].Name]));
break;
default:
throw new Exception("Missed data type in sql select ");
}
}
ret.Add(o);
}
return ret.ToArray();
}
Related
I got the above error in my app. Here is the original code
public string GetCustomerNumber(Guid id)
{
string accountNumber =
(string)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidmyApp,
CommandType.StoredProcedure,
"GetCustomerNumber",
new SqlParameter("#id", id));
return accountNumber.ToString();
}
I replaced with
public string GetCustomerNumber(Guid id)
{
object accountNumber =
(object)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidCRM,
CommandType.StoredProcedure,
"spx_GetCustomerNumber",
new SqlParameter("#id", id));
if (accountNumber is System.DBNull)
{
return string.Empty;
}
else
{
return accountNumber.ToString();
}
}
Is there a better way around this?
With a simple generic function you can make this very easy. Just do this:
return ConvertFromDBVal<string>(accountNumber);
using the function:
public static T ConvertFromDBVal<T>(object obj)
{
if (obj == null || obj == DBNull.Value)
{
return default(T); // returns the default value for the type
}
else
{
return (T)obj;
}
}
A shorter form can be used:
return (accountNumber == DBNull.Value) ? string.Empty : accountNumber.ToString()
EDIT: Haven't paid attention to ExecuteScalar. It does really return null if the field is absent in the return result. So use instead:
return (accountNumber == null) ? string.Empty : accountNumber.ToString()
ExecuteScalar will return
null if there is no result set
otherwise the first column of the first row of the resultset, which may be DBNull.
If you know that the first column of the resultset is a string, then to cover all bases you need to check for both null and DBNull. Something like:
object accountNumber = ...ExecuteScalar(...);
return (accountNumber == null) ? String.Empty : accountNumber.ToString();
The above code relies on the fact that DBNull.ToString returns an empty string.
If accountNumber was another type (say integer), then you'd need to be more explicit:
object accountNumber = ...ExecuteScalar(...);
return (accountNumber == null || Convert.IsDBNull(accountNumber) ?
(int) accountNumber : 0;
If you know for sure that your resultset will always have at least one row (e.g. SELECT COUNT(*)...), then you can skip the check for null.
In your case the error message "Unable to cast object of type ‘System.DBNull’ to type ‘System.String`" indicates that the first column of your result set is a DBNUll value. This is from the cast to string on the first line:
string accountNumber = (string) ... ExecuteScalar(...);
Marc_s's comment that you don't need to check for DBNull.Value is wrong.
You can use C#'s null coalescing operator
return accountNumber ?? string.Empty;
This is the generic method that I use to convert any object that might be a DBNull.Value:
public static T ConvertDBNull<T>(object value, Func<object, T> conversionFunction)
{
return conversionFunction(value == DBNull.Value ? null : value);
}
usage:
var result = command.ExecuteScalar();
return result.ConvertDBNull(Convert.ToInt32);
shorter:
return command
.ExecuteScalar()
.ConvertDBNull(Convert.ToInt32);
There is another way to workaround this issue. How about modify your store procedure? by using ISNULL(your field, "") sql function , you can return empty string if the return value is null.
Then you have your clean code as original version.
I suppose you can do it like this:
string accountNumber = DBSqlHelperFactory.ExecuteScalar(...) as string;
If accountNumber is null it means it was DBNull not string :)
String.Concat transforms DBNull and null values to an empty string.
public string GetCustomerNumber(Guid id)
{
object accountNumber =
(object)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidCRM,
CommandType.StoredProcedure,
"spx_GetCustomerNumber",
new SqlParameter("#id", id));
return String.Concat(accountNumber);
}
However, I think you lose something on code understandability
Since I got an instance which isn't null and if I compared to DBNULL I got Operator '==' cannot be applied to operands of type 'string' and 'system.dbnull' exeption,
and if I tried to change to compare to NULL, it simply didn't work ( since DBNull is an object) even that's the accepted answer.
I decided to simply use the 'is' keyword.
So the result is very readable:
data = (item is DBNull) ? String.Empty : item
based on answer from #rein
public static class DbDataReaderExtensions
{
public static TObjProp Get<TObj, TObjProp>(
this DbDataReader reader,
Expression<Func<TObj, TObjProp>> expression)
{
MemberExpression member = expression.Body as MemberExpression;
string propertyName = member.Member.Name;
//PropertyInfo propInfo = member.Member as PropertyInfo;
var recordOrdinal = reader.GetOrdinal(propertyName);
var obj = reader.GetValue(recordOrdinal);
if (obj == null || obj == DBNull.Value)
{
return default(TObjProp);
}
else
{
return (TObjProp)obj;
}
}
}
Given:
public class MyClass
{
public bool? IsCheckPassed { get; set; }
}
Use as:
var test = reader.Get<MyClass, bool?>(o => o.IsCheckPassed);
or, if you hardcode class type in exception method:
var test = reader.Get(o => o.IsCheckPassed);
p.s. I haven't figured yet how to make generics implicit without sacrificing code length.. fee free to comment and suggest improvements
Full example:
public async Task<MyClass> Test(string connectionString) {
var result = new MyClass();
await using var con = new SQLiteConnection(connectionString);
con.Open();
await using var cmd = con.CreateCommand();
cmd.CommandText = #$"SELECT Id, IsCheckPassed FROM mytable";
var reader = await cmd.ExecuteReaderAsync();
while (reader.Read()) {
// old, not working! Throws exception!
//bool? isCheckPassed1 = reader.GetBoolean(reader.GetOrdinal("IsCheckPassed"));
// old, working, but too long (also if you have like 20 properties then all the more reasons to refactor..)
bool? isCheckPassed2 = null;
bool? isCheckPassed2Temp = reader.GetValue(reader.GetOrdinal("IsCheckPassed"));
if (isCheckPassed2Temp != null && isCheckPassed2Temp != DBNull.Value)
isCheckPassed2 = (bool?)isCheckPassed2Temp;
// new
var isCheckPassed3 = reader.Get<MyClass, bool?>(o => o.IsCheckPassed);
// repeat for 20 more properties :)
result.IsCheckPassed = isCheckPassed3;
}
return result;
}
Solution will work for as long as table column names match property names of the class. And might not be production-grade performance wise, so use or modify at your own risk :)
A more concise approach using more recent C# syntax and also accounting for nullable types:
private static T? FromDbNull<T>(object? obj) =>
obj == null || obj == DBNull.Value ? default : (T)obj;
Can be used with a data reader as follows:
while (reader.Read())
{
var newObject = new SomeObject(
FromDbNull<string?>(reader["nullable_field_1"]),
FromDbNull<string?>(reader["nullable_field_2"]),
FromDbNull<string?>(reader["nullable_field_3"]),
FromDbNull<double?>(reader["nullable_field_4"])
);
response.Add(newObject);
}
I use an extension to eliminate this problem for me, which may or may not be what you are after.
It goes like this:
public static class Extensions
{
public String TrimString(this object item)
{
return String.Format("{0}", item).Trim();
}
}
Note:
This extension does not return null values! If the item is null or DBNull.Value, it will return an empty String.
Usage:
public string GetCustomerNumber(Guid id)
{
var obj =
DBSqlHelperFactory.ExecuteScalar(
connectionStringSplendidmyApp,
CommandType.StoredProcedure,
"GetCustomerNumber",
new SqlParameter("#id", id)
);
return obj.TrimString();
}
Convert it Like
string s = System.DBNull.value.ToString();
I am trying to read a boolean value from ODBCDataReader by using ODBCDataReader.GetBoolean(i) in theory it should work fine as the column we have read is a boolean column and the data coming in is either 1 or 0
so when I read it like this -
return reader.IsDBNull(col) ? null : reader.GetBoolean(col);
I get InvalidCastException
Then I tried to fetch reader as
return reader.IsDBNull(col) ? null : Convert.ToBoolean(reader.GetValue(col));
The above line complains about The string is not recognized as a valid bool value.(value is "1")
Then I wrote the following method --
public static bool? GetDbSafeNullableBit(this IDataRecord reader, string propertyName, bool? defaultBool = null)
{
var returnValue = defaultBool;
var col = reader.GetOrdinal(propertyName);
if (reader.IsDBNull(col))
{
return returnValue;
}
var stringValue = reader[col].ToString();
switch (stringValue)
{
case "1":
returnValue = true;
break;
case "0":
returnValue = false;
break;
default:
throw new Exception($"Value read from reader is not defined as proper boolean, value is {stringValue}");
}
return returnValue;
}
and Voila, I find this works fine.
Can someone please guide me on why the first two ways are not giving the expected result.
PS: Data is coming from PostgresSql (makes any difference ?)
Consider the following code snippet
public class Class1
{
public enum TestEnum
{
Value1 = 1,
Value2 = 2
}
public void TestCall()
{
/*some standard DB code returning an SqlDataReader...*/
SqlDataReader rdr = com.ExecuteReader();
Item item = new Item();
/*original code*/
/*Database "Type" is a varchar() field containing an integer, please dont ask why :)*/
if (rdr["Type"].GetType() == typeof(DBNull))
{
item.Type = TestEnum.Value1;
}
else if ((string)rdr["Type"] == "1")
{
item.Type = TestEnum.Value2;
}
else if ((string)rdr["Type"] == "2")
{
item.Type = TestEnum.Value1;
}
else
{
item.Type = TestEnum.Value1;
}
/*suggested code*/
item.Type = rdr["Type"] as TestEnum? ?? TestEnum.Value1; //<- default / null value to use
}
}
public class Item
{
public Class1.TestEnum Type;
}
During code review, a colleague of mine pointed out that i could replace the cascading IFs ("original code") with a single line ("suggested code")
While the suggested code runs just fine, I get a NullReferenceException when inspecting "rdr["Type"] as TestEnum?" at debug time.
I was wondering if this is a sign of underlying problems with the suggested code, what is the prefered way of mapping a database value to an enum, and what are your thoughts on this kind of code generally speaking.
The suggested code is just wrong - it will not throw exception, but will always evaluate to TestEnum.Value1.
Why? The reader returns the value as object. The as T? operator will evaluate to non null value only if the object represents a boxed T value. When the object contains string as in your case, or even if was a boxed int (the underlying type of your enum), still the operator as TestEnum? will evaluate to null, hence the expression will hit the ?? TestEnum.Value1 condition.
Shortly, don't rely on such tricks. If you want to improve that code, create a method (which can be reused from other places if needed):
static TestEnum ToTestEnum(object dbValue)
{
TestEnum value;
return Enum.TryParse(dbValue as string, out value) ? value : TestEnum.Value1;
}
and then change the original code like
item.Type = ToTestEnum(rdr["Type"]);
When I try to get an Integer from my SQLite db I can only get it working by reading it as a string and then run int.Parse on it.
Is this right, I read something about this having to do with ExeculeScalar possibly giving back null?
Here is my current code SendSQLExecScalar() sends the command string etc. and return an object
public object SendSQLExecScalar(string C)
{
OpenConnection();
SQLiteCommand SQLCommand = new SQLiteCommand(C, DbConnection);
try
{
object Output = SQLCommand.ExecuteScalar();
CloseConnection();
return Output;
}
catch (Exception X)
{
MessageBox.Show(X.Message);
return null;
}
}
And:
int ID = int.Parse(SendSQLExecScalar(C).ToString());
EDIT :
Specified cast is not valid.
public static int GetImageID(string Path)
{
string C = "SELECT ID FROM Images WHERE Path LIKE '" + Path + "' LIMIT 1";
return ConvertFromDBVal<int>(SendSQLExecScalar(C));
}
public static T ConvertFromDBVal<T>(object obj)
{
if (obj == null || obj == DBNull.Value)
{
return default(T);
}
else
{
return (T)obj; //breaks here saying this cast is invalid
}
}
I read something about this having to do with execuleScalar possibly
giving back null?
Yes, if there is no data that your sql query returns, ExecuteScalar returns null reference.
If you are 100% sure the return value on the first column of the first row is already int, you can just cast it like;
int ID = (int)SendSQLExecScalar(C);
To prevent null cases on this method, I almost always uses rein's generic method as;
public static T ConvertFromDBVal<T>(object obj)
{
if (obj == null || obj == DBNull.Value)
{
return default(T); // returns the default value for the type
}
else
{
return (T)obj;
}
}
Use TryParse instead of Parse, this allows you to test whether something is parseable.
If you use int.Parse() with an invalid int, you'll get an exception while in the TryParse, it returns a boolean letting you know whether the parse succeeded or not.
In short use Parse if you are sure the value will be valid; otherwise use TryParse.
int number = int.Parse(someString);
int number;
int.TryParse(someString, out number);
(int)(long)SendSQLExecScalar(C);
Solved with this, looks Like SQLite integer will return a long object which I needed to unpack before casting it to an int.
I want to give a parameter to a method and i want my method to return data by looking the parameter. Data can be in type of boolean, string, int or etc. How can i return a variable type from a method? I don't want to return an object type and then cast it to another type. For example:
BlaBla VariableReturnExampleMethod(int a)
{
if (a == 1)
return "Demo";
else if (a == 2)
return 2;
else if (a == 3)
return True;
else
return null;
}
The reason why i want that is i have a method that reads a selected column of a row from the database. Types of columns are not same but i have to return every column's information.
How can i return a variable type from a method? I don't want to return an object type and then cast it to another type.
Well that's basically what you do have to do. Alternatively, if you're using C# 4 you could make the return type dynamic, which will allow the conversion to be implicit:
dynamic VariableReturnExampleMethod(int a)
{
// Body as per question
}
...
// Fine...
int x = VariableReturnExampleMethod(2);
// This will throw an exception at execution time
int y = VariableReturnExampleMethod(1);
Fundamentally, you specify types to let the compiler know what to expect. How can that work if the type is only known at execution time? The reason the dynamic version works is that it basically tells the compiler to defer its normal work until execution time - so you lose the normal safety which would let the second example fail at compile time.
Use dynamic Keyword in place of BlahBlah if you are targeting .Net 4.0 but if lesser one then object is your safest bet because it is the base class for every other class you can think of.
It sounds like this might be a good case for generics. If you know what data type you're expecting when you call it, you can call that particular generic version of the function.
Consider using something like Dapper-dot-net (written by Marc Gravell and Sam Saffron at our very own Stack Overflow) to pull things out of the DB. It handles the database to object mapping for you.
Furthermore, if you don't want to use a tool, and you're pulling from a Database, and you know the data types of the various columns at compile time (like it sounds you do), you should probably be working row-by-row rather than column-by-column.
//Pseudo-code:
List<DatabaseObject> objects = new List<DatabaseObject>();
foreach(var row in DatabaseRows)
{
var toAdd = new DatabaseObject();
toAdd.StringTypeVariable = "Demo";
toAdd.IntTypeVariable = 2;
toAdd.BoolTypeVariable = true;
object.Add(toAdd);
}
Note: you could use object initializer syntax, and linq here but this is the most basic way I could think of demoing this without using a ton of extra stuff.
Also note, that here I'm assuming that you don't actually want to return "Demo", 2, and true, but values that use the row. That just means you'd change the hard coded values to: row.GetStringType(stringColumnIdx) or something similar.
Use return type as object, then you are able to get any return type. you have to handle the return type ether through reflection or other method.
check this:
void Main()
{
object aa = VariableReturnExampleMethod(3);
Console.WriteLine(aa.ToString());
}
object VariableReturnExampleMethod(int a)
{
if (a == 1)
return "Demo";
else if (a == 2)
return 2;
else if (a == 3)
return true;
else
return null;
}
Edit:
I am in the favor of strongly typed objects and you can implement it easily on .net platform.
if(returnedValue !=null)
{
string currentDataType = returnedValue.GetType().Name;
object valueObj = GetValueByValidating(currentDataType, stringValue);
}
public object GetValueByValidating(string strCurrentDatatype, object valueObj)
{
if (valueObj != "")
{
if (strCurrentDatatype.ToLower().Contains("int"))
{
valueObj = Convert.ToInt32(valueObj);
}
else if (strCurrentDatatype.ToLower().Contains("decimal"))
{
valueObj = Convert.ToDecimal(valueObj);
}
else if (strCurrentDatatype.ToLower().Contains("double") || strCurrentDatatype.ToLower().Contains("real"))
{
valueObj = Convert.ToDouble(valueObj);
}
else if (strCurrentDatatype.ToLower().Contains("string"))
{
valueObj = Convert.ToString(valueObj);
}
else
{
valueObj = valueObj.ToString();
}
}
else
{
valueObj = null;
}
return valueObj;
}
I look on your asks and one is better than second, but last i must rewritting to better understand solution. And this solution skiped long if else stack and replacing it by foreach on Types enum, where we can implement all types what we need. I more like using dynamic, but this is usable too.
Main function GetValueByValidating returned value if is type defined and possible, in other cases return false
Look niranjan-kala this is your main function after rewriting.
///
/// Enum of wanted types
///
public enum Types
{
[ExtendetFlags("int")]
INT,
[ExtendetFlags("decimal")]
DECIMAL,
[ExtendetFlags("double")]
DOUBLE,
[ExtendetFlags("real")]
REAL,
[ExtendetFlags("string")]
STRING,
[ExtendetFlags("object")]
OBJECT,
[ExtendetFlags("null")]
NULLABLE
}
///
/// Cycle by types when in enum exist string reference on type (helper)
///
///
///
public static Types GetCurrentType(string container)
{
foreach (Types t in Enum.GetValues(typeof(Types)))
{
if (container.Contains(t.GetFlagValue()))
{
return t;
}
}
return Types.NULLABLE;
}
///
/// Return object converted to type
///
///
///
///
public static object GetValueByValidating(string strCurrentDatatype, object valueObj)
{
var _value = valueObj != null ? valueObj : null;
try
{
Types _current = _value != null ? GetCurrentType(strCurrentDatatype.ToLower()) : Types.NULLABLE;
switch (_current)
{
case Types.INT:
valueObj = Convert.ToInt32(valueObj);
break;
case Types.DECIMAL:
valueObj = Convert.ToDecimal(valueObj);
break;
case Types.DOUBLE:
valueObj = Convert.ToDouble(valueObj);
break;
case Types.REAL:
valueObj = Convert.ToDouble(valueObj);
break;
case Types.STRING:
valueObj = Convert.ToString(valueObj);
break;
case Types.OBJECT:
break;
case Types.NULLABLE:
throw new InvalidCastException("Type not handled before selecting, function crashed by retype var.");
}
} catch (InvalidCastException ex)
{
Log.WriteException(ex);
valueObj = false;
}
return valueObj;
}