tags:

views:

1265

answers:

8

I have a series of Extension methods to help with null-checking on IDataRecord objects, which I'm currently implementing like this:

public static int? GetNullableInt32(this IDataRecord dr, int ordinal)
{
    int? nullInt = null;
    return dr.IsDBNull(ordinal) ? nullInt : dr.GetInt32(ordinal);
}

public static int? GetNullableInt32(this IDataRecord dr, string fieldname)
{
    int ordinal = dr.GetOrdinal(fieldname);
    return dr.GetNullableInt32(ordinal);
}

and so on, for each type I need to deal with.

I'd like to reimplement these as a generic method, partly to reduce redundancy and partly to learn how to write generic methods in general.

I've written this:

public static Nullable<T> GetNullable<T>(this IDataRecord dr, int ordinal)
{
    Nullable<T> nullValue = null;
    return dr.IsDBNull(ordinal) ? nullValue : (Nullable<T>) dr.GetValue(ordinal);
}

which works as long as T is a value type, but if T is a reference type it won't.

This method would need to return either a Nullable type if T is a value type, and default(T) otherwise. How would I implement this behavior?

A: 

I do it this way:

DataRow record = GetSomeRecord();
int? someNumber = record[15] as int?
Guid? someUID = record["MyPrimaryKey"] as Guid?;
string someText = GetSomeText();
record["Description"] = someText.ToDbString();

// ........

public static class StringExtensionHelper {
    public static object ToDbString( this string text ) {
         object ret = null != text ? text : DBNull.Value
         return ret;
    }
}

EDIT: You can ( or you should ) have a "ToDbInt32, ToDbBool, etc..." extension methods for other primitive types ofcourse.

EDIT 2: You can also extend the base class "object" with "ToDbValue".

public static class StringExtensionHelper {
    public static object ToDbValue( this object value ) {
         object ret = object.ReferenceEquals( value, null ) ? (object)DBNull.Value : value;
         return ret;
    }
}
TcKs
I could, but the whole point of this generics exercise is to avoid writing a separate method for each datatype. And I don't see how your example is even pertinent... I'm extending IDataRecord to null check data taken _from_ the datastore.
Adam Lassek
If you want check values from record, you can use "as" keyword, which do what you want. If is in record DbNull, then null will be returned. Otherwise the will be returned "int" as "Nullable<int>". No special method is required.
TcKs
You should **not** extend object with an extension method as it won't be available in all languages, like VB.NET
Scott Dorman
@Scott Dorman: Yes, but only if you need CLS compliance. .NET has a lot of languages, be care about all of them is **IMHO** not good way.
TcKs
A: 

The Nullable structure is only for Value types, because reference types are nullable anyway...

KiwiBastard
Which is why I can't make this return Nullable<T>, which is why I asked the question.
Adam Lassek
+6  A: 

You can just declare your method like this:

public static T GetNullable<T>(this IDataRecord dr, int ordinal)
{
    return dr.IsDBNull(ordinal) ? default(T) : (T) dr.GetValue(ordinal);
}

This way, if T is a nullable int or any other nullable value type, it will in fact return null. If it's a regular datatype, it will just return the default value for that type.

BFree
Its is very bad solution. If is in datarecord NULL value, you does not want a default value of int. The NULL and ZERO are different values.
TcKs
IF the value in the database is nullable, then the T would be a Nullable<int> in which case NULL is the default value that would be returned.
BFree
If so, why do this extension method, if you can use simply "dr[ordinal] as int?" ?
TcKs
Hey, I didn't ask the question. I'm just giving my suggestion. This method will work for value types, nullable value types, and reference types. That was the question. This was my answer...
BFree
I tried this, works as advertised.
David B
dr[ordinal] will throw an exception if it's DBNull.
Adam Lassek
It looks like I was making things more complicated than they needed to be. This should do what I want, thanks for the help.
Adam Lassek
this code breaks CA1004 (FxCop), how many people care about that? (just asking)
Juan Zamudio
@juan That seems like a pretty superfluous error. I disagree with FxCop on this one -- explicitly stating the type of a generic method is not a bad thing.
Adam Lassek
+1  A: 

I don't think you can implement this with a single function. If C# supported overloading based on return type, you might be able to, but even then I would recommend against doing so.

You should be able to accomplish the same thing by not using nullable data types and return either an actual value or null, as suggested by BFree.

Scott Dorman
Actually, you can have two overloaded methods that only differ in return types. This is legit syntax: public string Method() { return ""; } public int Method() { return 0; }
BFree
@BFree : Type 'xyz' already defines a member called 'Method' with the same parameter types.
David B
@BFree: I just tried your exact code and got the following error: Type 'Program1' already defines a member called 'Method' with the same parameter types.
Scott Dorman
The parameter types are the same, but the return types are different, and that is legit syntax. Try it out....
BFree
OK, I stand corrected. For some reason I was convinced you CAN do that. Mah bad....
BFree
+1  A: 

This works:

public static T Get<T>( this IDataRecord dr, int ordinal) 
{
 T  nullValue = default(T);
 return dr.IsDBNull(ordinal) ? nullValue : (T) dr.GetValue(ordinal);
}


public void Code(params string[] args)
{
 IDataRecord dr= null;
 int? a = Get<int?>(dr, 1);
 string b = Get<string>(dr, 2);
}
James Curran
This is pretty much the same as BFree's answer. The sample usage is helpful.
David B
A: 

You can't do it with one method but you do it with three:

public static T GetData<T>(this IDataReader reader, Func<int, T> getFunc, int index)
{
    if (!reader.IsClosed)
    {
        return getFunc(index);
    }
    throw new ArgumentException("Reader is closed.", "reader");
}

public static T GetDataNullableRef<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : class
{
    if (!reader.IsClosed)
    {
        return reader.IsDBNull(index) ? null : getFunc(index);
    }
    throw new ArgumentException("Reader is closed.", "reader");
}

public static T? GetDataNullableValue<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : struct
{
    if (!reader.IsClosed)
    {
        return reader.IsDBNull(index) ? (T?)null : getFunc(index);
    }
    throw new ArgumentException("Reader is closed.", "reader");
}

Then to use it you would do:

private static Whatever CreateObject(IDataReader reader)
{
    Int32? id = reader.GetDataNullableValue<Int32>(reader.GetInt32, 0);
    string name = reader.GetDataNullableRef<string>(reader.GetString, 1);
    Int32 x = reader.GetData<Int32>(reader.GetInt32, 2);
}
Logicalmind
A: 
public static T Get<T>(this IDataRecord rec, Func<int, T> GetValue, int ordinal)
{
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(ordinal);
}

or more performant

public static T Get<T>(this IDataRecord rec, Func<IDataRecord, int, T> GetValue, int ordinal)
{
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(rec, ordinal);
}

public static Func<IDataRecord, int, int> GetInt32 = (rec, i) => rec.GetInt32(i);
public static Func<IDataRecord, int, bool> GetBool = (rec, i) => rec.GetBoolean(i);
public static Func<IDataRecord, int, string> GetString = (rec, i) => rec.GetString(i);

and use it like this

rec.Get(GetString, index);
rec.Get(GetInt32, index);
SeeR
You've managed to implement a generic function without realizing the slightest benefit from it. Not having to write a separate function for each type is the _whole point_ of generics, and the reason for this question being posted.
Adam Lassek
Maybe, but the difference from your solution is that you have only one method where you check for nullability and avoid casting which may be costly if you have really large datasets.
SeeR
Also comparing to BFree solution you avoid boxing
SeeR
+1  A: 

I can't understand why the need to over complicate this whole process. Why not keep it nice and simple and use the following lines of code:

For value types where null is valid use int? iNullable = dr[ordinal] as int?;.

For value types where null is invalid use int iNonNullable = dr[ordinal] as int? ?? default(int);.

For reference types use string sValue = dr[ordinal] as string;.

For anyone who thinks that the code wont work and that dr[ordinal] will throw an exception for DBNull here is an example method that once given a valid connection string will prove the concept.

private void Test()
{
  int? iTestA;
  int? iTestB;
  int iTestC;
  string sTestA;
  string sTestB;

  //Create connection
  using (SqlConnection oConnection = new SqlConnection(@""))
  {
    //Open connection
    oConnection.Open();

    //Create command
    using (SqlCommand oCommand = oConnection.CreateCommand())
    {
      //Set command text
      oCommand.CommandText = "SELECT null, 1, null, null, '1'";

      //Create reader
      using (SqlDataReader oReader = oCommand.ExecuteReader())
      {
        //Read the data
        oReader.Read();

        //Set the values
        iTestA = oReader[0] as int?;
        iTestB = oReader[1] as int?;
        iTestC = oReader[2] as int? ?? -1;
        sTestA = oReader[3] as string;
        sTestB = oReader[4] as string;
      }
    }
  }
}
Stevo3000
@Stevo as I already explained to BFree in the comments, dr[ordinal] throws an exception if it's DBNull. Your example won't work.
Adam Lassek
@Adam Lassek - I don't know why you think my code wont work, have you tried it? I have it running on production systems, please actually check the code before down-voting. I think most noobs could tell you that `dr[ordinal]` wont throw an exception if the value is DBNull.
Stevo3000
@Stevo what framework version? At the time I wrote this question, I was using 3.5 and it definitely threw an exception if the field was DBNull.
Adam Lassek
@Stevo http://stackoverflow.com/questions/1855556/datareader-best-practices/1855564#1855564
Adam Lassek
@Adam Lassek - Well I've tested it on both .NET 2.0 and .NET 3.5 (repeatedly) and it works flawlessly. You will NOT get an exception reading DBNull from a DataReader into a compatible object type or by using the `as` keyword to cast as type or set null. I suggest you make a dummy app and run the method above to prove this. You can also use `object oObject = oReader[0];` to prove that you can access a DBNull object directly from the reader.
Stevo3000