2

i'm using EF 4.3.1, I've overridden the SaveChanges() on the context so that I can get a list of the objects and their states and create entries in my audit log table. I need to store the id of the record in the audit log table so i have a reference to it. This is a problem when records are inserted, as I don't have access to the id before it's saved. Is there any way of getting the id at that point?

public override int SaveChanges()
{
        ChangeTracker.DetectChanges();

        var objectStateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
        var modifiedAuditableEntities = objectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added).Where(e => (IAuditable)e.Entity != null);

        foreach (var entry in modifiedAuditableEntities)
        {
            var entity = (IAuditable)entry.Entity;

            if (entity != null)
            {
                switch (entry.State)
                {
                    case EntityState.Added:
                        entity.IsAdded = true;
                        break;
                    case EntityState.Deleted:
                        entity.IsDeleted = true;
                        break;
                    case EntityState.Modified:
                        entity.IsModified = true;
                        break;
                }

                this.EntitySet<AuditLogEntry>().Add(this.auditLogService.CreateAuditLogEntryForEntity((IAuditable)entry.Entity));
            }
        return base.SaveChanges();
}
newbie_86
  • 4,520
  • 17
  • 58
  • 89
  • How does it get its ID? If it's generated by the database, then it doesn't even exist until SaveChanges – podiluska Oct 17 '12 at 12:14
  • yes, its generated by the database. this is a problem. i could create my entries for the audit log based on the entity state, then save the actual entities to the database, and then possibly update the audit log entries with the entity id. But how would i link up the saved item to the audit log entry...this is my problem...any ideas? – newbie_86 Oct 17 '12 at 12:31

3 Answers3

5

If your goal is that you want both your save, and your audit log to be created at the same time, you could wrap it in a transaction scope so your method to be atomic.

    public override int SaveChanges()
    {
        ChangeTracker.DetectChanges();
        using (var scope = new TransactionScope())
        {
            var objectStateManager = ((IObjectContextAdapter) this).ObjectContext.ObjectStateManager;
            var modifiedAuditableEntities =
                objectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added).Where(
                    e => (IAuditable) e.Entity != null);
            var result = base.SaveChanges();
            foreach (var entry in modifiedAuditableEntities)
            {
                var entity = (IAuditable) entry.Entity;

                if (entity != null)
                {
                    switch (entry.State)
                    {
                        case EntityState.Added:
                            entity.IsAdded = true;
                            break;
                        case EntityState.Deleted:
                            entity.IsDeleted = true;
                            break;
                        case EntityState.Modified:
                            entity.IsModified = true;
                            break;
                    }

                    this.EntitySet<AuditLogEntry>().Add(
                        this.auditLogService.CreateAuditLogEntryForEntity((IAuditable) entry.Entity));
                }
            }
            base.SaveChanges();

            scope.Complete();
            return result;
        }
    }
Mark Oreta
  • 10,346
  • 1
  • 33
  • 36
  • Thanks! Is there any disadvantage to using a transaction scope? Also, how would I get the entity id for my log record? In my auditlogservice can I just use logEntry.EntityId = entry.Entity.Id? Will that be good enough to wire it up? – newbie_86 Oct 17 '12 at 12:40
  • The only drawback I've experienced is that you *might* call up [MSDTC](http://msdn.microsoft.com/en-us/library/windows/desktop/ms684146(v=vs.85).aspx) but that depends on what exactly you're doing. The Id property of your entry will be loaded once you call the first SaveChanges. – Mark Oreta Oct 17 '12 at 12:43
  • Sorry, i'm not following...so setting logEntry.EntityId = entry.Entity.Id in my service before the first SaveChanges() is called won't set the id? – newbie_86 Oct 17 '12 at 12:52
  • You want to set the Id After the first save changes, before the 2nd. Because it's wrapped in a transaction scope, the changes wont be committed until you complete the scope, so you don't have to worry about concurrency and things. – Mark Oreta Oct 17 '12 at 13:10
  • so where you have SaveChanges() thats actually base.SaveChanges()? but at that point the item is not yet pushed to the db, so it still has no id? Between SaveChanges() and scope.Complete() do I need to manually set the id on my auditlogEntry? I was doing this in the line above SaveChanges()...seems like I need to do it after? – newbie_86 Oct 17 '12 at 13:15
  • yeah it's base.SaveChanges() sorry. Keep in mind, in the code sample there are **two** save changes. The first one will get the Id's for your entities in the created transaction. The 2nd one, is what will save your audit log entries. After that, you commit your changes by completing the scope. – Mark Oreta Oct 17 '12 at 13:31
2

Id the Id is an integer generated by the database there isnt any way of getting it before calling save changes. Possible solutions include:

  • Triggers
  • Using Guids as Primary Keys
  • Using a key generation strategy not managed by the database (HiLo in NHibernate)
  • Calling base.SaveChanges() first then inspecting the results.
JeffreyABecker
  • 2,724
  • 1
  • 25
  • 36
1

I would recommend using a stored procedure at the database level to perform your insert/auditing functions (and other database changes), and revoking the insert/update/delete permission from your users. As such you can guarantee the integrity of the update process and of the audit table.

podiluska
  • 50,950
  • 7
  • 98
  • 104