Thursday, April 17, 2014

Quick and Efficient Database Library

In recent projects the need to continually re-write database access routines came up over and over.  I got tired of redoing the same code and so I wrote these quick database functions and extensions.  They are fully unit-tested and I'll include those at the end.

I think they could be made slightly more efficient using lambda expressions to handle the try-catch block, but that is a future enhancement that I might make.

For now, here's the first function: ExecuteScalar.  The power of these functions is that they are not tied to a specific database type such as SqlServer.  The use the generic System.Data interfaces for all external objects and even when they throw an exception they throw the generic DbException (from which exceptions such as SqlException inherit).  This allows you to theoretically connect and execute commands on a wider range of relational database types (though I have not tested it against MySql, Oracle, etc).

The function parameters are quite simple and straight-forward.  Pass in a connection, the name of the stored procedure (or the inline sql if you wish).  Optional arguments include a parameter list, an error message to include if an exception is thrown, and if you wish to trap any exceptions you can.

Code Snippet
  1. public object ExecuteScalar(IDbConnection dbConnection, string storedProcedureName, IEnumerable<IDataParameter> parameters = null,
  2. string errorMessage = ""bool throwException = true)
  3. {
  4.   object result = null;
  5.   try
  6.   {
  7.     using (IDbCommand dbCommand = dbConnection.CreateCommand())
  8.     {
  9.       SetupDbCommand(dbConnection, dbCommand, storedProcedureName, parameters);
  10.       dbCommand.Connection.Open();
  11.       result = dbCommand.ExecuteScalar();
  12.       dbCommand.Connection.CleanupConnection();
  13.     }
  14.   }
  15.   catch (DbException ex)
  16.   {
  17.     dbConnection.CleanupConnection(); 
  18.     if (throwException)
  19.       throw;
  20.   }
  21.  
  22.   return result;
  23. }

This works great if all you are expected to be returned is a row count or a single value, but you need more if you expect an actual data-set. In that situation we have:

Code Snippet
  1. public T ExecuteQuery<T>(IDbConnection dbConnection, string storedProcedureName, Func<IDataReader, T> translateFunction, IEnumerable<IDataParameter> parameters = nullstring errorMessage = "", bool throwException = true) where T : class, new()
  2. {
  3.   if (translateFunction == null) throw new ArgumentNullException("translateFunction");
  4.  
  5.   try
  6.   {
  7.     using (IDbCommand dbCommand = dbConnection.CreateCommand())
  8.     {
  9.       SetupDbCommand(dbConnection, dbCommand, storedProcedureName, parameters);
  10.       dbConnection.Open();
  11.       using (IDataReader dbReader = dbCommand.ExecuteReader())
  12.       {
  13.         var result = new T();
  14.         if (dbReader != null)
  15.           result = translateFunction.Invoke(dbReader);
  16.  
  17.         dbCommand.Connection.CleanupConnection();
  18.  
  19.         return result;
  20.       }
  21.     }
  22.   }
  23.   catch (DbException ex)
  24.   {
  25.     dbConnection.CleanupConnection();
  26.  
  27.     if (throwException)
  28.       throw;
  29.   }
  30.  
  31.   return null;
  32. }

This function takes a data reader and passes it to a custom translation function you pass in.  The translation function is responsible for retrieving the data from the reader and creating the object or objects you wish to return.  In most cases I have used this by creating a static translate function on the class itself.  I have also created a variant of this function to handle returning an IEnumerable<> of the object type.

Finally, one key component these functions don't handle is transaction management.  For that, for a unit of work, you'd need to wrap the calls in your own transaction statements complete with commit and rollback handling.

Enjoy and as usual I welcome and encourage feedback!

1 comment :