Pages

Thursday, October 24, 2013

Lights, Camera, Action Delegates!

I recently worked on a project that a number of other developers collaborated on. All of the contributors to the project are very smart people, and I was impressed by much of the work that they had done. But one thing caught my eye, and I want to write a post about it since there are folks out there that are not familiar with this technique.

The Func and Action delegates were introduced in .Net 3.5 and they have a variety of uses. One of the best uses that I have found for them is to separate procedural code from actual business logic. Specifically, I like to create methods that accept an action delegate to control database, Entity Framework, or Linq to Sql contexts, and WCF service operations.

In the project I was working on I repeatedly found classes in the data layer with code like this (in this example a connection object has already been created and passed into the class) :

IDbCommand dbCommand = connection.CreateCommand();
dbCommand.CommandText = "Command_Text";

try
{
    if (dbCommand.Connection.State == ConnectionState.Closed)
        dbCommand.Connection.Open();

    dbCommand.ExecuteNonQuery();
}
catch (Exception ex)
{
    Log.DefaultLog.WriteVerbose(ex.Message + Environment.NewLine + ex.StackTrace);
    throw ex;
}
finally
{
    if (dbCommand != null && dbCommand.Connection != null && dbCommand.Connection.State == ConnectionState.Open)
        dbCommand.Connection.Close();
}


Nothing wrong with the code itself, it was just the fact that it was repeated so many times throughout the project. I saw this and knew immediately that we could clean things up and centralize all of our connection and command management into one place by using Action delegates. All that was needed was to create a method that accepted and Action delegate as a parameter (and in this case a database connection and string for the command text as well) :

public void ExecuteCommand(IDbConnection connection, string commandText, Action<IDbCommand> command)
{
    using (IDbCommand dbCommand = connection.CreateCommand())
    {
        dbCommand.CommandText = commandText;

        if (dbCommand.Connection.IsDatabaseOnline())
        {
            try
            {
                if (dbCommand.Connection.State == ConnectionState.Closed)
                    dbCommand.Connection.Open();

                command.Invoke(dbCommand);
            }
            catch (Exception ex)
            {
                Log.DefaultLog.WriteVerbose(ex.Message + Environment.NewLine + ex.StackTrace);
                throw ex;
            }
            finally
            {
                if (dbCommand.Transaction == null && dbCommand.Connection != null && dbCommand.Connection.State == ConnectionState.Open)
                    dbCommand.Connection.Close();
            }
        }
        else
        {
            throw new Exception("The database is not online.");
        }
    }
}
BTW: The dbCommand.Connection.IsDatabaseOnline() method call is an extension method I use in many projects... it can be ignored in this example.

So, with this method in place, all that needed to be done in order to execute a command is the following:

ExecuteCommand(connection, "Command_Text_Goes_Here", command =>
{
    command.ExecuteNonQuery();
});

Now you don't have to worry about managing the connection, no more worrying about catching connection errors, no more worrying about logging, and finally, your code becomes cleaner and easier to read!

But, this specific example is just that, an example of one way to use an Action delegate to centralize functionality. I have used the same principle to encapsulate EF, Linq to SQL, and WCF operations. And there are plenty of other areas this technique can be applied. The takeaway of this post is that if you need to centralize operation code, consider using the Action delegate. It may be exactly what you need to get the job done.

Here are some links if you would like to find out more:


No comments:

Post a Comment