34
votes

I use Entity Framework 4 and I have parent - child relation with "Cascade Delete" set. So i would expect when i remove a child from the parent that the child is deleted when i call SaveChanges().

        cuRepository.Attach(_controlUnit);
        foreach (var recipe in recipes) {
            _controlUnit.Recipes.Remove(recipe);
            //repository.DeleteObject(recipe);
        }

Instead i get an error:

System.InvalidOperationException occurred Message=The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

When I explicitly delete the children (see commented line), all is fine. What am I missing?

5
I had the same problem today and I believe this is a design flaw in Entity Framework. The relationship between the tables in SQL Server states "Cascade delete orphans" and therefore should work. It works like that in NHibernate. Luckily you can get it to work following GraemeMiller's answer and related questions.Andre Luus

5 Answers

30
votes

You aren't deleting the object with the remove statement. Instead you are attempting to alter a record and make it an orphan (by setting the foreign key to null). The database has a non-null constraint on that column and prevents you from doing so.

27
votes

http://weblogs.asp.net/zeeshanhirani/archive/2010/07/23/removing-entity-from-a-related-collection.aspx explains exactly what happened to you.


Assuming you have a class design something like this:

sample class design

Entity Framework will generate the required foreign key columns and add NOT NULL constraints to them because all Recipes will always be associated with exactly one ControlUnit.

So at runtime you will have objects similar to the following layout:

object diagram at runtime

Now your code comes into play and deleted the relationship between the Recipe objects and their ControlUnit:

objects with deleted relationships

Trying to save at this moment, the database does not have a ControlUnit ID to put into the foreign key NOT NULL column. The current object state violates the class diagram above and cannot be saved to a database layout that has been generated under the assumption that every Recipe is associated with one ControlUnit. This is why the database refuses to save the changes and you see the exception.

This also explains why it works when you uncomment the line deleting the entity: The entity is removed from the database along with its relationship, so no constraints are violated, therefore no exception.

"But I set ON DELETE CASCADE on the relationship..."

Yes, but that is only triggered on deletion of the object, not on deletion of the relationship. With ON DELETE CASCADE set, this should work:

controlUnitRepository.DeleteObject(_controlUnit);
// deletes the ControlUnit and all associated Recipe entities

If you want to trigger deletion of the Recipe entities on deletion of their relationship with ControlUnit, your relationship should not be a simple association but rather a composition:

updated class diagram with composition

EF does not support this natively, but you can emulate the behaviour using identifying relationships. Once an entity is in an identifying relationship to a parent entity and that relationship is removed, the entity is removed as well. It seems this was your intention from the start. For more information on identifying relationship, see Implementing identifying relationships with EF4 where I implemented identifying relationships with EF4 and have linked to more reading material.

11
votes

add context.DeleteObject(recipe) inside the loop

8
votes

If you make the relationship between child and parent an identifying one then you can remove child entities from the collection. You need to make the child's key a composite key containing the primary id key of the parent. That way EF knows it needs to remove the child.

Identifying relationship basically says if the parent doesn't exist then the child has no meaning. This means EF knows it is safe to delete the child when the relationship is removed.

See this question Identifying Relationship and inserting child entities causes "Cannot insert explicit value for identity column in table" and this one Is it possible to remove child from collection and resolve issues on SaveChanges?

3
votes

I use this extension in order not to add a method in DAL just to delete an entity (code taken from http://blogs.msdn.com/b/alexj/archive/2009/06/08/tip-24-how-to-get-the-objectcontext-from-an-entity.aspx):

public static void Delete<T>(this EntityCollection<T> collection, T entityToDelete) where T : EntityObject, IEntityWithRelationships
{
    RelationshipManager relationshipManager = entityToDelete.RelationshipManager;

    IRelatedEnd relatedEnd = relationshipManager.GetAllRelatedEnds().FirstOrDefault();
    if (relatedEnd == null)
    {
        throw new Exception("No relationships found for the entity to delete. Entity must have at least one relationship.");
    }

    var query = relatedEnd.CreateSourceQuery() as ObjectQuery;
    if (query == null)
    {
        throw new Exception("The entity to delete is detached. Entity must be attached to an ObjectContext.");
    }

    query.Context.DeleteObject(entityToDelete);
    collection.Remove(entityToDelete);
}

So I then delete an entity like Order.Products.Delete(prod).

Constraints to use the extension are:
- Entity must have relationships;
- Entity must be attached to ObjectContext.