Search code examples
c#servicestackormlite-servicestack

Are interceptors possible in ServiceStack.OrmLite?


I would like to create a hook for database inserts or updates in OrmLite.

Lets say I want to write a CreateDate for every record which is inserted to the database and a LastUpdateDate for every update without database triggers or anything like that. I see no way of doing this if I call Db.Save<Anything>(anyList) with anyList containing new and changed objects.

I know that NHibernate does provide interceptors to inject custom logic for each object before it gets persisted. Is there any way to achieve this in ServiceStack.OrmLite?


Solution

  • I thought this was a good feature that was missing so I've just added support for Insert and Update Filters to OrmLite. So now you can configure a global Update or Insert filter with:

    public interface IAudit
    {
        DateTime CreatedDate { get; set; }
        DateTime ModifiedDate { get; set; }
        string ModifiedBy { get; set; }
    }
    
    OrmLiteConfig.InsertFilter = (dbCmd, row) =>
    {
        var auditRow = row as IAudit;
        if (auditRow != null)
        {
            auditRow.CreatedDate = auditRow.ModifiedDate = DateTime.UtcNow;
        }
    };
    
    OrmLiteConfig.UpdateFilter = (dbCmd, row) =>
    {
        var auditRow = row as IAudit;
        if (auditRow != null)
        {
            auditRow.ModifiedDate = DateTime.UtcNow;
        }
    };
    

    So now the Created and Modified date fields will be updated on every row that implements IAudit and is inserted in any of OrmLite's Typed APIs (i.e. not dynamic SQL or partial updates using anon types), e.g:

    Table Definition

    public class AuditTableA : IAudit
    {
        [AutoIncrement]
        public int Id { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime ModifiedDate { get; set; }
        public string ModifiedBy { get; set; }
    }
    
    public class AuditTableB : IAudit
    {
        [AutoIncrement]
        public int Id { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime ModifiedDate { get; set; }
        public string ModifiedBy { get; set; }
    }
    

    var a = new AuditTableA();
    var b = new AuditTableB();
    db.Save(a);
    db.Save(b);
    
    var insertRowA = db.SingleById<AuditTableA>(a.Id);
    var insertRowB = db.SingleById<AuditTableB>(b.Id);
    
    //both insertRowA/insertRowB CreatedDate/ModifiedDate fields populated
    

    Validation

    The filters can also be used for validation where throwing an exception will prevent the operation and bubble the exception, e.g:

    OrmLiteConfig.InsertFilter = OrmLiteConfig.UpdateFilter = (dbCmd, row) =>
    {
        var auditRow = row as IAudit;
        if (auditRow != null)
        {
            if (auditRow.ModifiedBy == null)
                throw new ArgumentNullException("ModifiedBy");
        }
    };
    
    try
    {
        db.Insert(new AuditTableA());
    }
    catch (ArgumentNullException) {
       //throws ArgumentNullException
    }
    
    db.Insert(new AuditTableA { ModifiedBy = "Me!" }); //succeeds
    

    This feature is now available in ServiceStack's MyGet feed and will be available in the next v4.0.11+ release on NuGet.