Entity Framework 6: detect relationship changes

∥☆過路亽.° 提交于 2019-12-05 00:18:04


in my DbContext subclass I have overridden the SaveChanges() method so I can implement a sort of Trigger-like functionality (before the changes are actually saved). Now, in some of these triggers it is necessary to detect whether certain relationships have changed, regardless of many-to-many, one-to-one/zero etc.

I have read a number of posts on the internet, including some on this site, that mention that the DbContext API doesn't expose any means of getting relationship info. However, ObjectContext should be able to.

My SaveChanges method:

public override int SaveChanges()
    IEntity entity;

    var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
    var added = stateManager.GetObjectStateEntries(EntityState.Added).ToList();
    var updated = stateManager.GetObjectStateEntries(EntityState.Modified).ToList();
    var deleted = stateManager.GetObjectStateEntries(EntityState.Deleted).ToList();
    var unchanged = stateManager.GetObjectStateEntries(EntityState.Unchanged).ToList();

    while ((entity = _entitiesRequiringTriggering.FirstOrDefault(x => x.Value).Key) != null)
        _entitiesRequiringTriggering[entity] = false;
        var entry = ChangeTracker.Entries<IEntity>().SingleOrDefault(x => x.State != EntityState.Unchanged && x.Entity == entity);
        if (entry == null) continue;
        var trigger = Triggers.Triggers.GetTriggerForEntity(entry.Entity, this);
        if (trigger == null) continue;
        switch (entry.State)
            case EntityState.Added:
            case EntityState.Modified:
            case EntityState.Deleted:
    return base.SaveChanges();

Note the four variables added, updated, deleted and unchanged. According to what I've found so far, the GetObjectStateEntries is supposed to return a collection of ObjectStateEntry, which has a property IsRelationship.

I run the following code in a test application:

using (var db = container.Resolve<IDatabaseContext>())
    var cus = db.Query<Customer>().Single(x => x.Id == 1);
    var newAddress = db.Query<Address>().Single(x => x.Id == 5);

    cus.Address = newAddress; //also sets the foreign key property Customer.AddressId to its new corresponding value

When I inspect the code in SaveChanges after that call, I get what was expected: one result in the updated list, the Customer object. But at no point do I ever get an ObjectStateEntry for the relationship (one-to-one) Customer_Address.

I need to be able to detect as previously described when the relationship has changed. For normal scalar properties you would do this:

var changed = DbEntry.Property(x => x.Name).OriginalValue == DbEntry.Property(x => x.Name).CurrentValue;

But for reference properties that doesn't work obviously. Any ideas?


You can use this ExtensionMethod to get a list of relationships that changed

public static class DbContextExtensions
    public static IEnumerable<Tuple<object, object>> GetRelationships(
        this DbContext context)
        return GetAddedRelationships(context)

    public static IEnumerable<Tuple<object, object>> GetAddedRelationships(
        this DbContext context)
        return GetRelationships(context, EntityState.Added, (e, i) => e.CurrentValues[i]);

    public static IEnumerable<Tuple<object, object>> GetDeletedRelationships(
        this DbContext context)
        return GetRelationships(context, EntityState.Deleted, (e, i) => e.OriginalValues[i]);

    private static IEnumerable<Tuple<object, object>> GetRelationships(
        this DbContext context,
        EntityState relationshipState,
        Func<ObjectStateEntry, int, object> getValue)
        var objectContext = ((IObjectContextAdapter)context).ObjectContext;

        return objectContext.ObjectStateManager
                            .Where(e => e.IsRelationship)
                                    e => Tuple.Create(
                                            objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
                                            objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));