Revisiting The TypeSafeDataReader: A Full Decorator With Ordinal Caching, etc.

In my previous post on this subject, several people mentioned changing the type safe methods for my data reader class into extension methods on IDataReader. I had planned on doing this, but then I ran into a situation where I needed to implement caching of the column ordinal positions to squeak out just a little bit more performance in a scenario that has 4,000+ queries into the database. Because I needed caching of the column ordinal positions, an extension method set would not cut it (I only want to cache columns for the current data reader, not store a static list of ordinals for all data readers that are used, like an extension method would force me to do). To do this with my TypeSafeDataReader, I had to change it up a bit in terms of how and where it’s used. The changes include turning it into a full decorator pattern and adding the ability to retrieve column information as more than just the raw data in the column, in addition to ability to do the caching of column ordinals when I know that I am looking for more than 1 result, but prevent caching when I know there I am looking for only 1 result.

The ITypeSafeDataReader Interface

I extracted an interface for this, and had it inherit from IDataReader so that I could decorate the IDataReader with my strongly typed methods.

1:publicinterface ITypeSafeDataReader : IDataReader

2: {

3: ColumnInformation<bool> GetBoolean(string columnName);

4: ColumnInformation<int> GetInt32(string columnName);

5: ColumnInformation<string> GetString(string columnName);

6: ColumnInformation<decimal> GetDecimal(string columnName);

7: ColumnInformation<DateTime> GetDateTime(string columnName);

8: ColumnInformation<T> GetColumnInformation<T>(string columnName);

9: }

Using Data From The ColumnInformation Class

I use this class to give me all the information I need about a column. For example, there are scenarios where I need to know if the column data is DBNull or not, and do something different depending. The ColumnInformation class gives me a .HasValue property which tells me whether or not the column has a value (a column has a value when the column is not DBNull).

1:publicclass ColumnInformation<T>

2: {

3:publicstring ColumnName { get; set; }

4:publicint ColumnIndex { get; set; }

5:publicbool HasValue { get; set; }

6:public T Value { get; set; }

7:

8:public ColumnInformation(string columnName)

9: {

10: ColumnName = columnName;

11: }

12:

13:publicstaticimplicitoperator T(ColumnInformation<T> ci)

14: {

15: T value = default(T);

16:if (ci != null && ci.HasValue)

17:value = ci.Value;

18:returnvalue;

19: }

20: }

Notice that I also supplied an implicit conversion operator to the generics type. This allows me to directly assign a ColumnInformation variable to a property or parameter of the generics type without having to call .Value. For example, assuming a property called SomeInt is an integer value, I can use this code:

1: var myColumn = tsdr.GetInt32("MyColumn");

2: someObject.SomeInt = myColumn;

This ColumnInformation class also let’s me check for values and do something other than map to a property if I need to. For example, I may not want to set a property at all if a value is null:

1: var childIdColumn = tsdr.GetInt32("ChildIdColumn");

2:if (childIdColumn.HasValue)

3: {

4: someObject.SomeProperty = LoadAChildObjectById(childIdColumn);

5: }

This would leave the SomeProperty null if the child id column does not have a value (is DBNull).

The TypeSafeDataReaderDecorator

It takes a standard IDataReader as a constructor parameter along with a boolean value that tells it whether or not to use column ordinal caching.I’ve used a few regions to wrap up the extraneous code that does nothing more than map to the underlying IDataReader calls. This makes it easier to hide that code and focus on the decorations that I’m adding in.

My core data access layer has methods that know whether I’m looking for a single result vs a list of results (for example, a method to get one object by id vs a method to get all of the objects for a given type). When the method that returns a single object decorates the IDataReader with my TypeSafeDataReaderDecorator, I pass a value of false into the constructor to tell it not to use column ordinal caching. This will prevent a few extra calls that don’t need to happen when I know it’s only 1 object being read. When the method that returns a list of objects decorates the IDataReader, though, it passes a value of true to the constructor so that the caching will take effect. This prevents the code from having to call GetOridinal on the underlying data reader multiple times for the same column. Note that the interface knows nothing about caching, though. That is purely an implementation concern.

Convenience Methods vs GetColumnInformation<T>

The specific method overloads for GetInt32, GetString, GetBoolean, etc. are simple convenience methods that do nothing more than call into the GetColumnInformation<T> method with the correct type. I added the convenience methods for the data types that our code is using the most often. It would be easy to add additional convenience methods as well, or you can just call GetColumnInformation<T> directly, telling it the type of data that you expect to be returned:

1:int myInt = tsdr.GetColumnInformation<int>("MyIntColumn");

Use ITypeSafeDataReader In Place Of IDataReader

I’ve updated my data access layer to always use the ITypeSafeDataReader specifically. This lets me have direct access to the extra functionality in the repository implementations without having to remember to decorate the IDataReader with the TypeSafeDataReaderDecorator where ever I need to use it. Since the ITypeSafeDataReader inherits from IDataReader, though, you can still pass it around as a standard IDataReader interface – you just lose the benefits of column ordinal caching, etc.