For auditing/history purposes, I am using the Entity Framework change tracker to determine, before writing changes, what has changed and serialize the changes. I can get the changed entities by calling this.ChangeTracker.Entries()
in my DbContext
derivative and looking at the values for anything marked EntityState.Added
, EntityState.Deleted
, or EntityState.Modified
. This all works great.
My problem is that this method does not work to track changes to collections of EF objects (for instance, an ICollection<Person>
property on a PersonGroup
object).
I'm sure the EF context must track this somehow -- how else would the database update work, after all? But is it available to me?
What you're looking for is relationship change tracking. You can find it in ObjectStateManager
of the underlying ObjectContext
, here is how you get all added relationships:
//you need to call DetectChanges
((IObjectContextAdapter)context).ObjectContext.DetectChanges();
var addedRelations = ((IObjectContextAdapter)context).ObjectContext
.ObjectStateManager.GetObjectStateEntries(EntityState.Added)
.Where(e=>e.IsRelationship).ToList();
It turns out you can get at the relationships with this code (assuming it's running inside your DbContext
derivative):
((IObjectContextAdapter) this).ObjectContext.ObjectStateManager
.GetObjectStateEntries(EntityState.Added)
.Where(e => e.IsRelationship)
.Select(r => new {EntityKeyInfo = r.CurrentValues[0],
CollectionMemberKeyInfo = r.CurrentValues[1], r.State});
Obviously you can tweak this based on what you need and it's up to do you something useful with it. The first two CurrentValues
entries represent EntityKey objects which will allow you to get the IDs of the entities in question.
If you want to deal with deleted entities this won't work and you need to use reflection. Instead of CurrentValues[0]
and CurrentValues[1]
you can look at the internal properties Key0
and Key1
, which are defined in an internal class you can't access at compile time. This will work: r.GetType().GetProperty("Key0", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(r, new object[0])
. Note that this is probably not an intended use and could blow up whenever.