0

I am creating an application with MVC4 and entity framework 5. How do can I implement this? I have looked around and found that I need to override SaveChanges .

Does anyone have any sample code on this? I am using code first approach.

As an example, the way I am saving data is as follows,

 public class AuditZoneRepository : IAuditZoneRepository
    {
        private AISDbContext context = new AISDbContext();



        public int Save(AuditZone model, ModelStateDictionary modelState)
        {
            if (model.Id == 0)
            {
                context.AuditZones.Add(model);
            }
            else
            {
                var recordToUpdate = context.AuditZones.FirstOrDefault(x => x.Id == model.Id);
                if (recordToUpdate != null)
                {
                    recordToUpdate.Description = model.Description;
                    recordToUpdate.Valid = model.Valid;
                    recordToUpdate.ModifiedDate = DateTime.Now;
                }
            }

            try
            {
                context.SaveChanges();
                return 1;
            }
            catch (Exception ex)
            {
                modelState.AddModelError("", "Database error has occured.  Please try again later");
                return -1;
            }
        }
    }
user2206329
  • 2,792
  • 10
  • 54
  • 81
  • Possible duplicate of [how to create an audit trail with Entity framework 5 and MVC 4](https://stackoverflow.com/questions/20961489/how-to-create-an-audit-trail-with-entity-framework-5-and-mvc-4) – Gert Arnold Nov 15 '19 at 12:17

1 Answers1

0

There is no need to override SaveChanges.

You can

Trigger Context.ChangeTracker.DetectChanges(); // may be necessary depending on your Proxy approach

Then analyze the context BEFORE save. you can then... add the Change Log to the CURRENT Unit of work. So the log gets saved in one COMMIT transaction. Or process it as you see fit. But saving your change log at same time. makes sure it is ONE Transaction.

Analyzing the context sample: I have a simple tool, to Dump context content to debug output so when in debugger I can use immediate window to check content. eg You can use this as a starter to prepare your CHANGE Log. Try it in debugger immediate window. I have FULL dump on my Context class.

Sample Immediate window call. UoW.Context.FullDump();

public void FullDump() {

        Debug.WriteLine("=====Begin of Context Dump=======");
        var dbsetList = this.ChangeTracker.Entries();
        foreach (var dbEntityEntry in dbsetList)
        {

            Debug.WriteLine(dbEntityEntry.Entity.GetType().Name + " => " + dbEntityEntry.State);
            switch (dbEntityEntry.State)
            {
                case EntityState.Detached:
                case EntityState.Unchanged:
                case EntityState.Added:
                case EntityState.Modified:
                    WriteCurrentValues(dbEntityEntry);
                    break;
                case EntityState.Deleted:
                    WriteOriginalValues(dbEntityEntry);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            Debug.WriteLine("==========End of Entity======");
        }
        Debug.WriteLine("==========End of Context======");
    }

    private static void WriteCurrentValues(DbEntityEntry dbEntityEntry)
    {
        foreach (var cv in dbEntityEntry.CurrentValues.PropertyNames)
        {
            Debug.WriteLine(cv + "=" + dbEntityEntry.CurrentValues[cv]);
        }
    }
    private static void WriteOriginalValues(DbEntityEntry dbEntityEntry)
    {
        foreach (var cv in dbEntityEntry.OriginalValues.PropertyNames)
        {
            Debug.WriteLine(cv + "=" + dbEntityEntry.OriginalValues[cv]);
        }
    }
}

EDIT: Get the changes

I use this routine to get chnages...

public class ObjectPair {
    public string Key { get; set; }
    public object Original { get; set; }
    public object Current { get; set; }
 }

public virtual IList<ObjectPair> GetChanges(object poco) {
        var changes = new List<ObjectPair>();
        var thePoco = (TPoco) poco;


        foreach (var propName in Entry(thePoco).CurrentValues.PropertyNames) {
            var curr = Entry(thePoco).CurrentValues[propName];
            var orig = Entry(thePoco).OriginalValues[propName];
            if (curr != null && orig != null) {
                if (curr.Equals(orig)) {
                    continue;
                }
            }
            if (curr == null && orig == null) {
                continue;
            }
            var aChangePair = new ObjectPair {Key = propName, Current = curr, Original = orig};
            changes.Add(aChangePair);
        }
        return changes;
    }

edit 2 If you must use the Internal Object tracking.

    var context =  ???// YOUR DBCONTEXT class
     // get objectcontext from dbcontext... 
     var   objectContext = ((IObjectContextAdapter) context).ObjectContext;
        // for each tracked entry
        foreach (var dbEntityEntry in context.ChangeTracker.Entries()) {
          //get the state entry from the statemanager per changed object
          var stateEntry =  objectContext.ObjectStateManager.GetObjectStateEntry(dbEntityEntry.Entity);
            var modProps = stateEntry.GetModifiedProperties();
            Debug.WriteLine(modProps.ToString());
        }

I decompiled EF6 . Get modified is indeed using private bit array to track fields that have been changed.

 // EF decompiled source..... _modifiedFields is a bitarray
 public override IEnumerable<string> GetModifiedProperties()
{
  this.ValidateState();
  if (EntityState.Modified == this.State && this._modifiedFields != null)
  {
    for (int i = 0; i < this._modifiedFields.Length; ++i)
    {
      if (this._modifiedFields[i])
        yield return this.GetCLayerName(i, this._cacheTypeMetadata);
    }
  }
}
phil soady
  • 11,043
  • 5
  • 50
  • 95
  • Phil - that is a great sample. Thank you. I have updated my code above, I am not using a unit of work. So you are saying before I save I should the change tracker to compare the two sets of values? also if I have different repositories, would I have to duplicate the code? – user2206329 Jan 06 '14 at 02:24
  • having many repositories is fine. The code is for the Context so it can be factored out. ie just pass in the context. Consider the unit of Work approach, where you control save, is where to control Change log. the reason UOW pattern exists is to avoid 500 save methods in repositories ;-) – phil soady Jan 06 '14 at 02:28
  • phil - is there an easy way to just identify just the modified values? In a record, I might only change 1 field. so does the current value function only display an item that has been modified or does it show all fields for that record? – user2206329 Jan 06 '14 at 02:36
  • Phil - this might sound silly, but surely when EF passes the data to be updated to the underlying table, it only sends update statements to the values that have been changed yeah? ie in a row of 10 fields, if only 1 field is updated, the sql sent to the table should be for only updating 1 field? if that is the case then would the ChangeTracker only give me the changed data? as i only wan to log these... – user2206329 Jan 06 '14 at 04:20
  • correct EF does Update Sets. let me see if i can quickly access that info – phil soady Jan 06 '14 at 05:17
  • here is a blog doing the same compare.. http://xharze.blogspot.com.au/2012/02/how-to-get-changed-properties-for.html and a SO post even going as deep as ObjectManager and still access original and Current values. This method looks useful though http://msdn.microsoft.com/en-us/library/system.data.objects.objectstateentry.getmodifiedproperties.aspx – phil soady Jan 06 '14 at 05:29