简体   繁体   中英

audit trail with entity framework

I have fields for audit trail in each table (InsertedBy, InsertedDate, UpdatedBy and UpdatedDate), I build solution to reduce redundant before by override savechange():

public override int SaveChanges()
{
    foreach (var entry in ChangeTracker.Entries().Where(e =>
        e.State == System.Data.Entity.EntityState.Added || e.State == System.Data.Entity.EntityState.Modified))
    {
        Auditing.ApplyAudit(entry, User);
    }
    return base.SaveChanges();
}

public class Auditing
{
    public static void ApplyAudit(DbEntityEntry entityEntry, int User)
    {
        Type type = entityEntry.Entity.GetType();
        if (entityEntry.State.ToString() == "Added")
        {
            if (type.GetProperty("InsertedBy") != null)
            {
                entityEntry.Property("InsertedBy").CurrentValue = User;
            }
            if (type.GetProperty("InsertedDate") != null)
            {
                entityEntry.Property("InsertedDate").CurrentValue = DateTime.Now;
            }
        }
        else if (entityEntry.State.ToString() == "Modified")
        {
            if (type.GetProperty("InsertedBy") != null)
            {
                entityEntry.Property("InsertedBy").IsModified = false;
            }
            if (type.GetProperty("InsertedDate") != null)
            {
                entityEntry.Property("InsertedDate").IsModified = false;
            }
            if (type.GetProperty("UpdatedBy") != null)
            {
                entityEntry.Property("UpdatedBy").CurrentValue = User;
            }
            if (type.GetProperty("UpdatedDate") != null)
            {
                entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
            }
        }
    }
}

the question is: is using reflection within each entity before modified or added waste in memory and performance ? if yes is there is best practice for this ? is this another code snippet better in performance or just use reflection also?

public static void ApplyAudit(DbEntityEntry entityEntry, long User)
{
    if (entityEntry.State.ToString() == "Added")
    {
        entityEntry.Property("InsertedBy").CurrentValue = User;
        entityEntry.Property("InsertedDate").CurrentValue = DateTime.Now;
    }
    else if (entityEntry.State.ToString() == "Modified")
    {
        entityEntry.Property("InsertedBy").IsModified = false;
        entityEntry.Property("InsertedDate").IsModified = false;
        entityEntry.Property("UpdatedBy").CurrentValue = User;
        entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
    }
}

is entityEntry.Property("InsertedBy") uses reflection ?

Reflection is slow (slow is subjective) and if you want to avoid it, then you need to get rid of such code as below:

Type type = entityEntry.Entity.GetType();
if (type.GetProperty("InsertedBy") != null)

Even if it was not slow, the code above is still "buggy" because a programmer may mistakenly write InsertBy instead of InsertedBy . This can easily be avoided with help from the compiler using the approach below.

Use an interface and implement it in all entities that require audit.

public interface IAuditable
{
    string InsertedBy { get; set; }
    // ... other properties
}

public class SomeEntity : IAuditable
{
    public string InsertedBy { get; set; }
}

public class Auditor<TAuditable> where TAuditable : IAuditable
{
    public void ApplyAudit(TAuditable entity, int userId)
    {
        // No reflection and you get compiler support
        if (entity.InsertedBy == null)
        {
            // whatever
        }
        else
        {
            // whatever
        }
    }
}

As mentioned in the comments, you will get compiler support and reflection is not used anymore. I would even go a step further and not pass the int userId . I will bring the code for figuring out the userId and put it in this class. That way the class is self sufficient and clients do not need to provide it this information.

Usage:

var e = new SomeEntity();
var auditor = new Auditor<SomeEntity>();
auditor.ApplyAudit(e, 1); // 1 is userId, I am just hardcoding for brevity

Or use it from your context:

public override int SaveChanges()
{
    var auditables = ChangeTracker.Entries().Where(e =>
        e.State == System.Data.Entity.EntityState.Added || e.State == System.Data.Entity.EntityState.Modified)
        .OfType<IAuditable>();
    var auditor = new Auditor<IAuditable>();
    foreach (var entry in auditables)
    {
        // 1 is userId, I am just hardcoding for brevity
        auditor.ApplyAudit(entry, 1);
    }
    return base.SaveChanges();
}

This means that all entities who are auditable will need to implement the IAuditable interface. EF generates partial classes for your entities but do not modify those partial classes because the next time you run the custom tool, it will be wiped out.

Instead, create another partial class with the same name and implement the IAuditable .

public partial class SomeEntity : IAuditable {}

An even better approach is to create a custom T4 template so it creates the partial class with the code : IAuditable . Please see this article for how to do that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM