Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic method to read data from DataReader

I am currently using this method to read data from DataReader -

private T GetValue<T>(object obj)
{
    if (typeof(DBNull) != obj.GetType())
    {
        return (T)obj;
    }
    return default(T);
}

calling above method as -

GetValue<int>(dataReader["columnName1"])
GetValue<string>(dataReader["columnName2"])
GetValue<float>(dataReader["columnName3"])

However this fails when columnName3 is having values as 7200000 with error
Invalid Cast Exception.

I am thinking to modify my method to replace -

return (T)obj;

with

return (T)Convert.ChangeType(obj, typeof(T));

But looking forward for a better way as this change will involve type casting two times.
Any better ideas?

Thank you!

like image 245
inutan Avatar asked Sep 07 '12 16:09

inutan


1 Answers

A generic method has the advantage that you can reduce a lot of code bloat, but otherwise churning out your own wrapper for each data type gives you the flexibility to have custom handling. And most probably your db queries will have a noticeable effect on performance than mode of retrieval.

I suggest you to write a set of your own extension methods than having one generic approach. Extending the method on IDataReader gives you the benefit of not propagating the methods on entire object sub types. I have had to individually handle types since various connectors behaved differently especially with Guid type. Also its hard to know if datareader read the value 0 or DBNull when you're returning 0 for both cases. Lets say there is an enum field in your table with a null value. why would you ever want it to be read as the first enum?

Just call:

dataReader.GetInt("columnName1")
dataReader.GetString("columnName3")
dataReader.GetFloat("columnName3")

And the methods:

public static int? GetInt(this IDataReader r, string columnName)
{
    var i = r[columnName];      
    if (i.IsNull())
        return null; //or your preferred value

    return (int)i;
}

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null || obj == DBNull.Value;
}

And similarly,

public static string GetString(this IDataReader r, string columnName)
{
}

public static float GetFloat(this IDataReader r, string columnName)
{
}

If you really want one generic function you can have it too.

public static T Get<T>(this IDataReader r, string columnName, T defaultValue = default(T))
{
    var obj = r[columnName];      
    if (obj.IsNull())
        return defaultValue;

    return (T)obj;
}

So call it

dataReader.Get<int>(1); //if DBNull should be treated as 0
dataReader.Get<int?>(1); //if DBNull should be treated as null
dataReader.Get<int>(1, -1); //if DBNull should be treated as a custom value, say -1

That said, the error is because you're not casting with right type as pointed out in comments. I could have gone with built in DBNull checks, but I don't to avoid data being read multiple times from the reader, inspired from this curious case of microoptimization

like image 74
nawfal Avatar answered Sep 28 '22 22:09

nawfal