I mentioned the Hole in the Middle pattern in my previous blog article, What’s In a Language? So, I wanted to actually use the pattern in C# with anonymous delegates, because that is the platform that I use at work primarily. One of the uses that jumped out was in a database layer. Typically when you are writing a database layer you have boilerplate code in every method to open and close connections and possibly handle exceptions and log exceptions. This is repetitive code that often gets pasted into every method. One approach for doing this is to create a code snippet that you can use from a snippet library. Another is to use the Hole in the Middle pattern.Here is a simple example of the idea:
public delegate T DatabaseCodeBlock<T>(SqlConnection conn); public class HoleInTheMiddle { ConnectionHelper connHelper =new ConnectionHelper(); public T ExecuteProcedure<T>(DatabaseCodeBlock<T> block) { SqlConnection conn = connHelper.GetSQLConnection(); try { return block(conn); } catch(Exception ex) { LogError(ex.ToString()); throw; } finally { conn.Close(); } } } public class DbLayer { HoleInTheMiddle holeInTheMiddle = new HoleInTheMiddle(); public DataTable GetCustomerByCustomerKey(int customerKey) { return holeInTheMiddle.ExecuteProcedure<DataTable>( conn => { RunSpParmsClass parm = new RunSpParmsClass(); parm.AppendEx("@CustomerID", SqlDbType. Int, ParameterDirection.Input, 4, customerKey); DataSet ds= dbHelper.RunSpGetDataset("GetCustomer", parm, conn); if (ds !=null && ds.Tables[0].Rows.Count > 0) { return ds.Tables[0]; } else { return null; } } ); } }
Now some of you are wondering why go through this trouble when you can use the Microsoft Data Access Application Block. Good question, but the point of this sample is to illustrate one way of using this design pattern. Now let’s take a look at the code. I have created a method ExecuteProcedure that has code to open and close a connection and to handle exceptions. It then fires a delegate of type DatabaseCodeBlock. This is the code block that we supply to fill the hole in this helper function. I am using generics to strongly type the return type of the DatabaseCodeBlock and the ExecuteProcedure method. This is not really neccessary as you could just set the value of a local variable in your anonymous delegate that you could return from the method that is calling ExecuteProcedure, but I think using generics makes for cleaner code.
Now the intersting part of this is the power of the anonymous delegate we are using for the DatabaseCodeBlock. Anonymous Delegates are what are known as closures and they can capture local variables and reference them in the code block. Even if this code block is executed in a another context. This allows one to not have to define different delegate interfaces if you want to use different patterns or to build some parameter array of inputs. Instead, you can just pass in your anonymous delegate and use any local variable in it magically. So in our example we are getting a Customer record by using the local customerKey variable. Now imagine we have a different stored procedure that we want to invoke that requires three or four parameters. If these are in the local scope of where the delegate is created then we can access them. Easy as pie! The last point is a subtle one. I suggest reading up on closures to get a better feel for some of the power of this way of doing things.