Friday, March 14, 2008

My IDataRecord Extension Methods

I decided it was high time I did a post with some actual code samples in it, so here we go.

I am really enjoying some of the new C# 3.0 language features, one in particular is extensions methods. If you are not yet familiar with extension methods, ScottGu has a good overview. I want to highlight some specific extension methods I've created for IDataRecord that I think are extremely handy, especially when you spend a lot of time working in the data layer with DataReaders like I do. I feel like I've written variations on the same data layer 100 times. Every time it evolves, when I went from .Net 1.1 to 2.0 I took heavy advantage of generics. Now in .Net 3.5 I am simplifying it even more with extensions.

When working with DataReaders you might often have a ton of code that looks like this:


string myString = record.GetString(record.GetOrdinal("MyField"));


Where record is an IDataRecord coming from a SqlDataReader or something similar. Now that's not a terrible line of code, but it gets pretty annoying typing record.GetOrdinal all the time. So to save me that annoyance I've added the following to my IDataRecordExtensions class:


public static long GetString(this IDataRecord record, string field)
{
return record.GetString(record.GetOrdinal(field));
}


An now all I have to call in my record handling code is this:


string myString = record.GetString("MyField");


Not ground-breaking, but better, and you can write an extension for all the types you are getting data for. Here's a better example of how extension methods for your IDataRecord can be more useful. Sometimes you might be dealing with a nullable type, IDataRecord doesn't handle this too gracefully, you end up having to write code that looks something like this:


string myString = null;
int ordinal = record.GetOrdinal("MyField");

if(!record.IsDBNull(ordinal))
{
myString = record.GetString(ordinal);
}


Again, not a ton of code, but if you have to do this hundreds of times it's going to get annoying and you are going to make mistakes. Now check this out, in my IDataRecordExtensions class I add the following two methods:


private static T GetNullable<T>(IDataRecord record, string field, Func<int, T> func)
{
T result = default(T);
int ordinal = record.GetOrdinal(field);

if (!record.IsDBNull(ordinal))
{
result = func(ordinal);
}

return result;
}

public static String GetNullableString(this IDataRecord record, string field)
{
return GetNullable<string>(record, field, x => record.GetString(x));
}


OK, what the heck is going on here. Lets look at GetNullableString first. This is an extension method for an IDataRecord that takes a field name and then calls this private GetNullable method. Check out that third argument though. In GetNullable its defined as Func<int, T> func. What we are doing is taking advantage of lambda support in C# 3.0 and we are saying, pass me a function that takes an int and returns me a generic T, and in this case T is a string. x => record.GetString(x) is saying passing the record's GetString method as that parameter. Now in our data handling code we can just write:


string myString = record.GetNullableString("MyField");


Nice. What's even better, is now we can easily add other extensions for other more interesting nullable types, such as double by adding code like this to the IDataRecordExtensions class:

public static double? GetNullableDouble(this IDataRecord record, string field)
{
return GetNullable<double?>(record, field, x => record.GetDouble(x));
}


So there you go. Hopefully my experiences with extension methods and IDataRecord are helpful to others.

1 comment:

mac said...

Hi Greg,

I've found this article very useful - while looking for an elegant way how to map readers to my business objects, I've tried several approaches and finally adopted that one of yours.

Anyway, I had to extend your solution for my purposes, as I'm using the same object for collection and single representations, while collection objects don't have all the attributes set (as the underlying SP returns only restricted set of columns).

But the basic idea of extending IDataRecord was great:)