Friday, December 12, 2008

Logging to Database With Enterprise Library Part III - Expression Cache

In my previous post we started to explore a solution for logging any arbitrary data to the database using the Enterprise Library.

Next on our list of things we need to create our Expressions. Not wanting to create the expressions each time we need to log the data, and not wanting to tie our method for extracting data from the log entry to the log entry, I opted to create what I am calling the AuditLoggerExpressionCache or Expression Cache for short. The expression cache uses the singleton pattern to ensure we only have one cache for our process.

The cache doesn't actually store the expressions directly but it stores a group of expressions, keyed off the AuditCategory.

public class AuditLoggerExpressionCache

{

    private Dictionary<string, IAuditLoggerExpressionGroup> _expressionGroups =

        new Dictionary<string, IAuditLoggerExpressionGroup>();

    private AuditLoggerExpressionCache() { }

 

    public void Add(IAuditLoggerExpressionGroup group)

    {

        this._expressionGroups.Add(group.Name, group);

    }

 

    public IAuditLoggerExpressionGroup this[string key]

    {

        get

        {

            return this._expressionGroups[key];

        }

    }

    #region Singleton Pattern

    public static AuditLoggerExpressionCache Instance

    {

        get

        {

            return singleton.Instance;

        }

    }

    private class singleton

    {

        internal static readonly AuditLoggerExpressionCache Instance = new AuditLoggerExpressionCache();

    }

    #endregion

 

    public AuditLoggerExpressionGroup<T> CreateGroup<T>(string category, string connection, string procedure)

    {

        return new AuditLoggerExpressionGroup<T>(category, connection, procedure);

    }

 

    public bool TryGetValue(string category, out IAuditLoggerExpressionGroup logGroup)

    {

        return this._expressionGroups.TryGetValue(category, out logGroup);

    }

}



I have defined an IAuditLoggerExpressionGroup interface which allows us to access the groups without having to know the generic parameter type. I am using the Generics to improve the syntax since you won't have to cast the AuditLogEntry in your lambda.

public interface IAuditLoggerExpressionGroup

{

string Name { get; set; }

string Connection { get; set; }

string Procedure { get; set; }

Delegate this[string key] { get; }

IList<string> Keys { get; }

object GetValue(string key, object param);

}



A couple of key properties. The name here is the audit category, the Connection is intended to be used to store the connection string, the Procedure is the name of the stored procedure to execute. Keys is the keys to the expression dictionary itself. I am using the keys also as the stored procedure parameter names.

Let's look at the ExpressionGroup:

public class AuditLoggerExpressionGroup<T> : IAuditLoggerExpressionGroup

{

 

    private Dictionary<string, Func<T, object>> _expressions =

        new Dictionary<string, Func<T, object>>();

 

    public AuditLoggerExpressionGroup(string name, string connection, string procedure)

    {

        this.Name = name;

        this.Connection = connection;

        this.Procedure = procedure;

    }

 

    #region IAuditLoggerExpressionGroup Members

 

    public string Name { get; set; }

    public string Connection { get; set; }

    public string Procedure { get; set; }

 

    public Func<T, object> this[string key]

    {

        get

        {

            return this._expressions[key];

        }

        set

        {

            this._expressions[key] = value;

        }

    }

    public IList<string> Keys

    {

        get

        {

            return this._expressions.Keys.ToList();

        }

    }

 

    public AuditLoggerExpressionGroup<T> Add(string key, Func<T, object> expression)

    {

        this._expressions.Add(key, expression);

        return this;

    }

 

    public AuditLoggerExpressionGroup<T> Cache()

    {

        Check.Require(!string.IsNullOrEmpty(this.Name), "You cannot cache something without a name");

        AuditLoggerExpressionCache.Instance.Add(this);

        return this;

    }

    public AuditLoggerExpressionGroup<T> Cache(string name)

    {

        this.Name = name;

        return this.Cache();

    }

 

    public object GetValue(string key, object param)

    {

        return this[key]((T)param);

    }

    Delegate IAuditLoggerExpressionGroup.this[string key]

    {

        get

        {

            return this[key] as Delegate;

        }

    }

    #endregion

 

}



One thing to note I am using the Design By Contract class. This is however optional but if your not using it, be sure to go have a look its a great additional to your programming toolbox. The expression group as you see in the Add method takes a key, and a Func. T is your specific AuditLogEntry class, and we are returning an object.

At this point your unit test should work, and the vast majority of the work has been done. The only thing left is to define and configure the actual trace listener. I will cover this in Part IV of this series.

No comments: