Tuesday, September 26, 2006

Previously I have posted an article: What You Should Dispose When Using ADO.NET. There I describe the common pattern for working with ADO.NET objects. Lets go a step further. I think that it is a very bad practice to return a DataReader object from a data layer to some layer above. The reason for this is that in my opinion you should not return move IDisposable objects between layers. Why? Ask yourself a question: who should dispose of such an object? The client layer where the object is used? Or maybe the data layer where the object originates from?

Given the best practices of object oriented programming (OOP) and encapsulation, you should not have to know the inner workings of the objects that you use. In this case you should not have to know if the DataReader is disposed for you or not etc.

But you have to return some kind of data! So what to return? Some time ago I have created a following pattern for running my queries on the database which in a simple form looks more or less like this:

public IEnumerable<DbDataRecord> ExecuteSelect(string commandText)
{
   
using (DbConnection connection = CreateConnection())
   {
      
using (DbCommand cmd = CreateCommand(commandText, connection))
      {
         connection.Open();
         
using (DbDataReader reader = cmd.ExecuteReader())
         
{
            
foreach (DbDataRecord record in reader)
            {
               
yield return record;
            }
         }
         connection.Close();
      }
   }
}

This way the client for this method ends up with a IEnumerable object that looks and acts like a DataReader but is not IDisposable. In fact when you work with DataReader you actually work with the DbDataRecord objects which the DataReader encapsulates.

Notice also the use of base classes such as DbConnection and DbCommand and not concrete implementations such as SqlCommand. This way you can plug any provider you want to this pattern and in fact this is what I do in the helper methods CreateConnection and CreateCommand:

private DbProviderFactory factory;
private DbProviderFactory Factory
{
   
get
   
{
      
if (factory == null)
      {
         factory =
DbProviderFactories.GetFactory(providerInvariantName);
      }
      
return factory;
   }
}
private DbConnection CreateConnection()
{
   
DbConnection connection = Factory.CreateConnection();
   connection.ConnectionString = connectionString;
   
return connection;
}
private DbCommand CreateCommand(string commandText, DbConnection connection)
{
   
DbCommand command = Factory.CreateCommand();
   command.Connection = connection;
   command.CommandText = commandText;
   
return command;
}

The DbProviderFactories and DbProviderFactory are new feature of ADO.NET 2.0 which are useful for making things more generic not that you couldn't do connection.CreateCommand() before ;-).

kick it on DotNetKicks.com

Friday, September 29, 2006 3:07:26 PM (Central European Standard Time, UTC+01:00)
I like that pattern. A minor disadvantage is that the client gets an IEnumerable and may not be aware that every time foreach is used (GetEnumerator() is called), an actual database call occurs. He may think that its just some collection with all objects loaded that he iterates over.
asbjorn
Friday, September 29, 2006 4:07:52 PM (Central European Standard Time, UTC+01:00)
Doing this defeats the purpose of DBDataReader being IDisposable. If you call ExecuteSelect but fail to loop through all the records (perhaps because the code for processing the DBDataRecord throws an exception), then the resources held by the DBDataReader will be leaked.

Take a look at what the spring.net folks are working on for an example of a better way to wrap ADO.NET.
http://www.springframework.net/presentations/SpringOne-DataAccess-Pollack.ppt
Nathan
Friday, September 29, 2006 6:32:03 PM (Central European Standard Time, UTC+01:00)
I take back my comments on resource leaks. I didn't realize that foreach loops automatically dispose their enumerators, or that disposing a compiler generated enumerator "executes any finally blocks as if the last executed yield return statement were a yield break statement."

Thanks for sharing this pattern.
Nathan
Sunday, October 01, 2006 8:12:31 PM (Central European Standard Time, UTC+01:00)
I think that foreach's dispose functionality is new to version 2.0.
Monday, May 12, 2008 11:17:50 AM (Central European Standard Time, UTC+01:00)
Hi

I like this pattern but I don't like the way you would have to call it when using stored procedures with lots of parameters.

I personally would like to create a command, set up the parameters and use that but i cant see an easy way to do that with this example. The only way i can see is by create an instance with a command property but then the nice staticness has gone.

What do you think? Any ideas?

Ben
Ben Crinion
Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview