Samantha J T Star
Samantha J T Star

Reputation: 32838

Auditing rows added to Azure table storage

I have created the following class which I believe gives me some good auditing capabilities for data rows in certain tables that require it. Here is the class I am using:

public class AuditableTableServiceEntity : TableServiceEntity  
{

    protected AuditableTableServiceEntity()
        : base()
    {
    }

    protected AuditableTableServiceEntity(string pk, string rk)
        : base(pk, rk) 
    { 
    }
    #region CreatedBy and ModifiedBy

    private string _CreatedBy;
    [DisplayName("Created By")]
    public string CreatedBy
    {
        get { return _CreatedBy; }
        set { _CreatedBy = value; Created = DateTime.Now; }
    }

    [DisplayName("Created")]
    public DateTime? Created { get; set; }

    private string _ModifiedBy;
    [DisplayName("Modified By")]
    public string ModifiedBy
    {
        get { return _ModifiedBy; }
        set { _ModifiedBy = value; Modified = DateTime.Now; }
    }

    [DisplayName("Modified")]
    public DateTime? Modified { get; set; }

    #endregion

}

Can anyone out there suggest any additional changes that I might consider for this class. I believe it is okay but as I need to implement this for many classes I would like to hear if anyone can suggest any changes or additions.

Upvotes: 0

Views: 1165

Answers (1)

Jeremy McGee
Jeremy McGee

Reputation: 25210

private string _ModifiedBy;

[DisplayName("Modified By")]
public string ModifiedBy
{
    get { return _ModifiedBy; }
    set { _ModifiedBy = value; Modified = DateTime.Now; }
}

will cause a stack overflow: setting the value of a property in a setter calls the setter, which sets the value of the property, which calls the setter, and so on.

You could set the properties in a constructor, but then things break if an instance is serialized and deserialized: when you deserialize it, the public parameterless constructor is called, and the setter is called... which sets the property to the date and time that the object was deserialized, not the stored value.


A better pattern might be to create another table for auditable events. This might look something like this:

public class Audit
{
    public string ModifiedBy { get; set; }
    public DateTime DateModified { get; set; }
    public Type ObjectType { get; set; }
    public string Field { get; set; }
    public object OldValue { get; set; }
    public object NewValue { get; set; }

    public static void Record(string user, Type objectType, object oldValue, object newValue)
    {
        Audit newEvent = new Audit
        {
            ModifiedBy = user,
            DateModified = DateTime.UtcNow,  // UtcNow avoids timezone issues
            ObjectType = objectType,
            OldValue = oldValue,
            NewValue = newValue
        };

        Save(newEvent);  // implement according to your particular storage classes
    }
}

Then, whenever you make changes to an object you want to audit, call Audit.Record() like so:

public class SomeKindOfAuditableEntity
{
    private string _importantFieldToTrack;

    public string ImportantFieldToTrack
    {
        get { return _importantFieldToTrack; }
        set
        {
            Audit.Record(GetCurrentUser(), this.GetType(), _importantFieldToTrack, value);
            _importantFieldToTrack = value;
        }
    }
}

This way you store a log of all changes that happen to all "interesting" properties of your tables. This has a few other advantages:

  • you see the old and new values of each change
  • the audit log is stored in a different place from the data itself, separating concerns
  • you don't need to have a base class for your data classes
  • the audit for old changes is kept around so you can go back through the entire log of the object's changes

The principal disadvantage is that you need to add code to each setter for each property you're interested in. There are ways to mitigate this with attributes and reflection and aspect-oriented programming -- see for instance Spring's implementation here: http://www.springframework.net/doc-latest/reference/html/aop.html -- in essence, you'd create an attribute for the properties you'd like to track.

The other disadvantage is that you'll consume lots of storage for the audit log - but you can have a background process that trims down the old entries periodically as you see fit.

Upvotes: 4

Related Questions