2

I'm trying to get the Audit:NET EntityFramework.Core extension to write an AuditLog entry per changed property.

For this purpose I've overidden the EntityFrameworkDataProvider.InsertEvent with a custom DataProvider.

The problem is, using DbContextHelper.Core.CreateAuditEvent to create a new EntityFrameworkEvent returns null.

The reason seems to be, at this point in the code execution DbContextHelper.GetModifiedEntries determines all EF Entries have State.Unmodified, even if they are clearly included in the EventEntry changes.

I'm trying to circumvent CreateAuditEvent by manually creating the contents is impossible due to private/internal properties.

Maybe there is an alternative solution to this problem I'm not seeing, i'm open to all suggestions.

Audit entity class

public class AuditLog
{
    public int Id { get; set; }
    public string Description { get; set; }
    public string OldValue { get; set; }
    public string NewValue { get; set; }
    public string PropertyName { get; set; }
    public DateTime AuditDateTime { get; set; }
    public Guid? AuditIssuerUserId { get; set; }
    public string AuditAction { get; set; }
    public string TableName { get; set; }
    public int TablePK { get; set; }
}

Startup configuration

Audit.Core.Configuration.Setup()
    .UseCustomProvider(new CustomEntityFrameworkDataProvider(x => x
        .AuditEntityAction<AuditLog>((ev, ent, auditEntity) =>
        {
            auditEntity.AuditDateTime = DateTime.Now;
            auditEntity.AuditAction = ent.Action;
            foreach(var change in ent.Changes)
            {
                auditEntity.OldValue = change.OriginalValue.ToString();
                auditEntity.NewValue = change.NewValue.ToString();
                auditEntity.PropertyName = change.ColumnName;
            }
        }

Custom data provider class

public class CustomEntityFrameworkDataProvider : EntityFrameworkDataProvider
{
    public override object InsertEvent(AuditEvent auditEvent)
    {
        var auditEventEf = auditEvent as AuditEventEntityFramework;
        if (auditEventEf == null)
            return null;

        object result = null;
        foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
        {
            if (entry.Changes == null || entry.Changes.Count == 0)
                continue;

            foreach (var change in entry.Changes)
            {
                var contextHelper = new DbContextHelper();
                var newEfEvent = contextHelper.CreateAuditEvent((IAuditDbContext)auditEventEf.EntityFrameworkEvent.GetDbContext());
                if (newEfEvent == null)
                    continue;
                newEfEvent.Entries = new List<EventEntry>() { entry };
                entry.Changes = new List<EventEntryChange> { change };
                auditEventEf.EntityFrameworkEvent = newEfEvent;
                result = base.InsertEvent(auditEvent);

            }
        }
        return result;
    }
}
ViRuSTriNiTy
  • 5,017
  • 2
  • 32
  • 58
mherberth
  • 23
  • 3

1 Answers1

2

Check my answer here https://github.com/thepirat000/Audit.NET/issues/323#issuecomment-673007204

You don't need to call CreateAuditEvent() you should be able to iterate over the Changes list on the original event and call base.InsertEvent() for each change, like this:

public override object InsertEvent(AuditEvent auditEvent)
{
    var auditEventEf = auditEvent as AuditEventEntityFramework;
    if (auditEventEf == null)
        return null;

    object result = null;
    foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
    {
        if (entry.Changes == null || entry.Changes.Count == 0)
            continue;

        // Call base.InsertEvent for each change
        var originalChanges = entry.Changes;
        foreach (var change in originalChanges)
        {
            entry.Changes = new List<EventEntryChange>() { change };
            result = base.InsertEvent(auditEvent);
        }
        entry.Changes = originalChanges;

    }
    return result;
}

Notes:

  • This could impact performance, since it will trigger an insert to the database for each column change.
  • If you plan to use async calls to DbContext.SaveChangesAsync, you should also implement the InsertEventAsync method on your CustomDataProvider
  • The Changes property is only available for Updates, so if you also want to audit Inserts and Deletes, you'll need to add the logic to get the column values from the ColumnValues property on the event
thepirat000
  • 12,362
  • 4
  • 46
  • 72