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
Monday, May 11, 2009 11:57:14 PM (Central European Standard Time, UTC+01:00)
Another downside to this approach is that you can actually return multiple results sets from the database to a datareader and gain access to them using the .NextResult() method, but by using the above code you lose the ability to gain access to these other results sets.

I do believe that the advice presented above (that you shouldn't pass IDisposable objects from one layer to another) is sound, but at the same time I do use multiple results sets in my data access code to eagerly load the first level foreign table objects from the database when bringing back the record (for example my UserAccount table has a foreign key column TeamId. I return the associated Team record in a second results set when I query for the UserAccount record to avoid having to query again later for it).

I would love to find a way to combine the two, but at present am stuck returning an IDataReader from the data access code.
Justin
Saturday, May 30, 2009 4:29:37 PM (Central European Standard Time, UTC+01:00)
Hi everyone. The dead might as well try to speak to the living as the old to the young.
I am from Micronesia and learning to read in English, please tell me right I wrote the following sentence: "Free search engine for suppliers of products and services."

With love :o, Abel.
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