3

I want to maintain all modified records and deleted records in my project and track their status using a Status column. Below is the code from my first attempt but it seems I cannot cast OriginalValues.ToObject() to DbEntityEntry for obvious reasons. How shoudl I go about accomplishing this?

    public override int SaveChanges()
    {
        var now = DateTime.Now;
        var currentUser = HttpContext.Current.User.Identity.Name;
        var trackedEntities = ChangeTracker.Entries<ITrackable>();

        foreach (var trackedEntity in trackedEntities)
        {
            switch(trackedEntity.State)
            {
                case System.Data.EntityState.Added:
                    trackedEntity.Entity.CreatedDate = now;                        
                    trackedEntity.Entity.CreatedBy = currentUser;                        
                    trackedEntity.Entity.Status = Status.Active;
                    break;
                case System.Data.EntityState.Modified:                        
                    var originalEntity = trackedEntity.OriginalValues.ToObject() as DbEntityEntry<ITrackable>;                        
                    originalEntity.Entity.ModifiedDate = now;
                    originalEntity.Entity.ModifiedBy = currentUser;
                    originalEntity.Entity.Status = Status.Modified;

                    var newEntity = trackedEntity.CurrentValues.ToObject() as DbEntityEntry<ITrackable>;
                    newEntity.State = System.Data.EntityState.Added;
                    newEntity.Entity.Status = Status.Active;                                                
                    break;
                case System.Data.EntityState.Deleted:
                    trackedEntity.State = System.Data.EntityState.Modified;
                    trackedEntity.Entity.Status = Status.Deleted;
                    break;                            
            }                
        }
        return base.SaveChanges();
    }
Mike
  • 5,437
  • 7
  • 45
  • 62
  • 2
    may be this is helpful http://stackoverflow.com/questions/6156818/entity-framework-4-1-dbcontext-override-savechanges-to-audit-property-change – Habib May 08 '12 at 16:14
  • Thank you definitely a help but I am hoping there is a way other than looping through the properties. – Mike May 08 '12 at 16:37

3 Answers3

2

I am not sure if I understand correctly what you are trying to achieve in the Modified case. But my understanding is that ToObject() creates a completely new object and fills it with the property values from the OriginalValues dictionary. So, originalEntity is not attached to the context and will just be garbage collected without any effect when the variable goes out of scope.

Therefore, don't you need to save the originalEntity as a new added entity to the database to save the last version before the modification is saved?

I would try this:

case System.Data.EntityState.Modified:                        
    var originalEntity = trackedEntity.OriginalValues.ToObject() as ITrackable;
    originalEntity.ModifiedDate = now;
    originalEntity.ModifiedBy = currentUser;
    originalEntity.Status = Status.Modified;
    Entry(originalEntity).Status = System.Data.EntityState.Added;

    trackedEntity.Entity.Status = Status.Active;
    break;

trackedEntity will be saved anyway because it is in state Modified and originalEntity would be created and saved as a new object which represents the state before modification.

I expect that the cast to ITrackable should work because ToObject() creates an object of the type which is represented by the OriginalValues dictionary, and that must be an ITrackable because you filter the ChangeTracker.Entries enumeration by this interface.

The above is untested, I am unsure if it will work.

Edit

The naming above is a bit confusing because originalEntity is an entity in your model while trackedEntity is a DbEntityEntry<T>. Maybe rename that either to originalEntityObject (ugly) or better trackedEntityEntry.

Just to mention: It's absolutely necessary (unless you are using change tracking proxies consequently) that you call ChangeTracker.DetectChanges() before you enter the foreach loop because otherwise the entries are possibly not in the correct final state and you enter wrong case sections or no section at all (when the state is Unchanged but still has changed properties). POCO changes are detected by comparison with the snapshot in OriginalValues and this last comparison is done in base.SaveChanges which is after your loop and too late. (See also "Edit 2" in the answer here: https://stackoverflow.com/a/6157071/270591.)

Community
  • 1
  • 1
Slauma
  • 175,098
  • 59
  • 401
  • 420
2

I was looking for how you should override SaveChanges() to implement a similar goal

it would be easier to cast like this;

((ITrackable)trackedEntity.Entity).CreatedDate = now;
khr055
  • 28,690
  • 16
  • 36
  • 48
Lance
  • 21
  • 1
0

Though not identical to the methodology you're using, I use the repository pattern for this. The rest of the app just instantiates the repository class, and calls Save(objToSave) or Delete(objToDelete) or similar. Now the execution process is disconnected from the actual data store, and I can take appropriate action: update the ModifyDate, change the Status, etc. If you're exposing the DataContext to the rest of the application, it's very hard to control the details under the hood.

robrich
  • 13,017
  • 7
  • 36
  • 63