Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to find all Entities that have had their relationships deleted?

I am trying to not have my Business Logic know the inner workings of my Data Layer and vica versa.

But Entity Framework is making that hard. I can insert into a collection (in my Business Layer) without a reference to the ObjectContext:

order.Containers.Add(new Container { ContainerId = containerId, Order = order });

And that saves fine when it comes time to do a SaveChanges() in the Data Layer.

But to delete an item from a collection I need a reference to the ObjectContext. (I am case #1 in this guide to deleting EF Entities.) If I just do this:

 delContainers.ForEach(container => order.Containers.Remove(container));

Then when I call SaveChanges() I get an exception telling me that I need to delete the object as well as the reference.

So, my options as I see it are:

  1. To pass a delegate to my Business Logic that will call the Entity Framework ObjectContext Delete method.
  2. Or (I am hoping) find a way to get all entities that have had their reference deleted and actually delete them. (Right before calling SaveChanges() in my data layer.)

Does anyone know a way to do that?

UPDATE:

I tried this:

// Add an event when Save Changes is called
this.ObjectContext.SavingChanges += OnSavingChanges; 

...

void OnSavingChanges(object sender, EventArgs e)
{
   var objectStateEntries = ObjectContext.ObjectStateManager
                                  .GetObjectStateEntries(EntityState.Deleted);
                                                         
   foreach (var objectStateEntry in objectStateEntries)
   {
       if (objectStateEntry.IsRelationship)
       {
            // Find some way to delete the related entity
       }
   }
}

But none even though I deleted a relationship, the set of deleted items is empty.

(I tried viewing all the items too and my relationship is not in there. Clearly there is something fundamental that I don't get about ObjectStateManager.)

like image 837
Vaccano Avatar asked Dec 16 '11 20:12

Vaccano


2 Answers

The correct solution for EF is point 3. from the linked article. It means propagating FK to principal entity into PK for dependent entity. This will form something called identifying relation which automatically deletes dependent entity when it is removed from the parent entity.

If you don't want to change your model and still want to achieve that in persistence ignorant way you probably can but it will work only for independent associations. Some initial implementation which works at least for my simple tested solution:

public partial class YourObjectContext
{
    public override int SaveChanges(SaveOptions options)
    {
        foreach (ObjectStateEntry relationEntry in ObjectStateManager
                                             .GetObjectStateEntries(EntityState.Deleted)
                                             .Where(e => e.IsRelationship))
        {
            var entry = GetEntityEntryFromRelation(relationEntry, 0);
            // Find representation of the relation 
            IRelatedEnd relatedEnd = entry.RelationshipManager
                                          .GetAllRelatedEnds()
                                          .First(r => r.RelationshipSet == relationEntry.EntitySet);

            RelationshipType relationshipType = relatedEnd.RelationshipSet.ElementType;
            if (!SkipDeletion(relationshipType))
            {
                // Now we know that model is inconsistent and entity on many side must be deleted
                if (!(relatedEnd is EntityReference)) // related end is many side
                {
                    entry = GetEntityEntryFromRelation(relationEntry, 1);
                }

                if (entry.State != EntityState.Deleted)
                {
                    context.DeleteObject(entry.Entity);
                }
            }
        }

        return base.SaveChanges();
    }

    private ObjectStateEntry GetEntityEntryFromRelation(ObjectStateEntry relationEntry, int index)
    {
        var firstKey = (EntityKey) relationEntry.OriginalValues[index];
        ObjectStateEntry entry = ObjectStateManager.GetObjectStateEntry(firstKey);
        return entry;
    }

    private bool SkipDeletion(RelationshipType relationshipType)
    {
        return
            // Many-to-many
            relationshipType.RelationshipEndMembers.All(
                r => r.RelationshipMultiplicity == RelationshipMultiplicity.Many) ||
            // ZeroOrOne-to-many 
            relationshipType.RelationshipEndMembers.Any(
                r => r.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne);
    }
}

To make it work your entities must be enabled for dynamic change tracking (all properties must be virtual and entity must be proxied) or you must manually call DetectChanges.

In case of foreign key associations the situation will be probably much worse because you will not find any deleted relation in the state manager. You will have to track changes to collections or keys manually and compare them to find discrepancies (I'm not sure how to do it in generic way) Foreign key association IMHO requires the identifying relation. Using FK properties already means that you included additional persistence dependency into your model.

like image 156
Ladislav Mrnka Avatar answered Sep 27 '22 19:09

Ladislav Mrnka


One way is to write a change handler in your data layer:

    private void ContainersChanged(object sender,
        CollectionChangeEventArgs e)
    {
        // Check for a related reference being removed. 
        if (e.Action == CollectionChangeAction.Remove)
        {
            Context.DeleteObject(e.Element);
        }
    }

There are many places you can wire this up -- in your object's constructor or repository get or SavingChanges or wherever:

    entity.Containers.AssociationChanged += new CollectionChangeEventHandler(ContainersChanged);

Now you can remove the association from elsewhere and it will "cascade" to the entity.

like image 39
Craig Stuntz Avatar answered Sep 27 '22 18:09

Craig Stuntz