12

I know it's several similar posts out there, but I cannot find any with a solution to this issue.

I want to add a (sort of) AudioLog when adding, changing or deleting entities (soft-delete) in Entity Framework 6. I've overridden the SaveChanges and because I only want to add log entries for EntityStates Added, Modified or Deleted, I fetch the list before I call SaveChanges the first time. The problem is, because I need to log what operation has been executed, I need to inspect the EntityState of the entities. But after SaveChanges is called, the EntityState is Unchanged for all entries.

public override int SaveChanges()
{
    using (var scope = new TransactionScope())
    {
        var modifiedEntries = ChangeTracker.Entries()
            .Where(e => e.State == EntityState.Added || e.State == EntityState.Deleted || e.State == EntityState.Modified)
            .ToList();

        int changes = base.SaveChanges();
        foreach (var entry in modifiedEntries)
        {
            ApplyAuditLog(entry);
        }

        base.SaveChanges();
        scope.Complete();
        return changes;
    }
}

private void ApplyAuditLog(DbEntityEntry entry)
{
    ILog entity = entry.Entity as ILog;

    if (entity != null)
    {
        LogOperation operation;
        switch (entry.State)
        {
            case EntityState.Added:
                operation = LogOperation.CreateEntity;
                break;
            case EntityState.Deleted:
                operation = LogOperation.DeleteEntity;
                break;
            case EntityState.Modified:
                operation = LogOperation.UpdateEntity;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

        AuditLog log = new AuditLog
        {
            Created = DateTime.Now,
            Entity = entry.Entity.GetType().Name,
            EntityId = entity.Id,
            Operation = operation,
        };

        AuditLog.Add(log);
    }
}
Saadi
  • 2,211
  • 4
  • 21
  • 50
baddaydaddy
  • 604
  • 8
  • 25
  • If the id is database generated, then you need to retrieve it **after** the `SaveChanges()` call, because before the id is simply not existant. – user2674389 Nov 05 '13 at 20:12
  • If you look at the code, that is exactly what I do. That is not the issue, it's the EntityState changing after SaveChanges. – baddaydaddy Nov 05 '13 at 20:42
  • 1
    Sorry, I overlooked that. After saving the EntityState will change, because the EntityState is not added or modified anymore - it's unmodified. So if you want to keep the EntityState, you have to store that data before saving - and retrieve the id after saving. – user2674389 Nov 05 '13 at 20:48
  • That I am aware of. But I hoped someone have a nice way of solving this. Since I do not have the id before the SaveChanges(), I can't figure out how I can map the right id to the right AuditLog object after the first SaveChanges(). Do I need to rely on the index position of each element? Or is there a cleaner alternative? If so, please provide an example of that. – baddaydaddy Nov 06 '13 at 08:17

2 Answers2

17

Ahhh... Off course!! The id will only be a "problem" for the entities that are newly added, so by splitting the list into two (one for modified/deleted and one for added), I create the AuditLog in two stages.

For anyone else who want to apply this kind of AuditLog, here is my working code:

public override int SaveChanges()
{
    using (var scope = new TransactionScope())
    {
        var addedEntries = ChangeTracker.Entries().Where(e => e.State == EntityState.Added).ToList();
        var modifiedEntries = ChangeTracker.Entries().Where(e => e.State == EntityState.Deleted || e.State == EntityState.Modified).ToList();

        foreach (var entry in modifiedEntries)
        {
            ApplyAuditLog(entry);
        }

        int changes = base.SaveChanges();
        foreach (var entry in addedEntries)
        {
            ApplyAuditLog(entry, LogOperation.CreateEntity);
        }

        base.SaveChanges();
        scope.Complete();
        return changes;
    }
}

private void ApplyAuditLog(DbEntityEntry entry)
{
    LogOperation operation;
    switch (entry.State)
    {
        case EntityState.Added:
            operation = LogOperation.CreateEntity;
            break;
        case EntityState.Deleted:
            operation = LogOperation.DeleteEntity;
            break;
        case EntityState.Modified:
            operation = LogOperation.UpdateEntity;
            break;
        default:
            throw new ArgumentOutOfRangeException();
    }

    ApplyAuditLog(entry, operation);
}

private void ApplyAuditLog(DbEntityEntry entry, LogOperation logOperation)
{
    ILog entity = entry.Entity as ILog;

    if (entity != null)
    {
        AuditLog log = new AuditLog
        {
            Created = DateTime.Now,
            Entity = entry.Entity.GetType().Name,
            EntityId = entity.Id,
            Operation = logOperation,
        };
        AuditLog.Add(log);
    }
}
Saadi
  • 2,211
  • 4
  • 21
  • 50
baddaydaddy
  • 604
  • 8
  • 25
  • 1
    You may want to wrap SaveChanges in a try/throw to catch an exception and back out the audit entries. If the commit fails the audit entries shouldn't exist – Charles Jan 14 '14 at 16:29
  • Actually, you need the id in case of addition, the audit must be related to original table for future investigation. – Costa Mar 08 '16 at 17:41
  • 1
    Why are u using 2 foreach ? instead you can do it once – Faris Dewantoro Jan 05 '20 at 06:45
0

you can save the EntityState to Entity key-value pair and use it after first changes.

var entries = this.ChangeTracker.Entries() .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified || e.State == EntityState.Deleted) .Select(e => new { e.State, e }).ToList();

x2da
  • 1
  • Please correct the formatting of your response [Editing Help](https://stackoverflow.com/editing-help) and ensure your question answers the original authors question. – Kwiksilver Apr 17 '21 at 16:41
  • Please read [How to answer](https://stackoverflow.com/help/how-to-answer) and update your answer. – fartem Apr 18 '21 at 08:05