The relationship could not be changed because one or more of the foreign-key properties is non-nullable
.net-4.0, c#, entity-framework-4, sql-server
Solution
@Slauma provided this answer in the above comments but asked me to post the answer.
The problem is that there is effectively a "bug" in the Self-Tracking Entities templates for Entity Framework (something Microsoft no longer recommends you use). A blog post specifically on this topic can be found here.
Specifically, the problem is that the Context's `ObjectStateManager` gets out of sync with the (attached) entities' `ChangeTracker.State` and you end up having objects with `entity.ChangeTracker.State == ObjectState.Deleted` but when the `context.ObjectStateManager` thinks that the state is set to `EntityState.Unchanged`. These two are clearly very different. So this fix effectively goes and looks for any object attached to the context as `EntityState.Unchanged` but digs down deeper and also checks each object's `ChangeTracker.State` for `ObjectState.Deleted` to fix things up.
An easy and very thoroughly-functional work-around for this problem (that has worked well for us) can be made in the Context's T4 template by replacing the `#region Handle Initial Entity State` block with the following code:
#region Handle Initial Entity State
var existingEntities = context
.ObjectStateManager
.GetObjectStateEntries(System.Data.EntityState.Unchanged)
.Select(x => x.Entity as IObjectWithChangeTracker)
.Where(x => x != null);
var deletes = entityIndex.AllEntities
.Where(x => x.ChangeTracker.State == ObjectState.Deleted)
.Union(existingEntities
.Where(x => x.ChangeTracker.State == ObjectState.Deleted));
var notDeleted = entityIndex.AllEntities
.Where(x => x.ChangeTracker.State != ObjectState.Deleted)
.Union(existingEntities
.Where(x => x.ChangeTracker.State != ObjectState.Deleted));
foreach (IObjectWithChangeTracker changedEntity in deletes)
{
HandleDeletedEntity(context, entityIndex, allRelationships, changedEntity);
}
foreach (IObjectWithChangeTracker changedEntity in notDeleted)
{
HandleEntity(context, entityIndex, allRelationships, changedEntity);
}
#endregion
Problem
(NOTE: This is not a duplicate of this question even though it has the same exception.) I have a poor man's transaction in place where the strategy is: - Insert a parent and child record. - Perform a long-running operation. - If long-running operation fails, go delete the previously-inserted parent and child records. When I attempt step 3, I get the following 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. I understand generally what this means, but I thought I was playing by the rules and no matter how hard I try to play by the rules, I'm unsure why I'm getting this message. We use self-tracking entities and my code is effectively this: ``` var parent = new Parent(1,2,3); var child = new Child(4,5,6); parent.Children.Add(child); MyContext.Parents.ApplyChanges(parent); MyContext.SaveChanges(SaveOptions.AcceptAllChangesAfterSave); // At this point, inserts were successful and entities are in an Unchanged state. // Also at this point, I see that parent.Children.Count == 1 var shouldDeleteEntities = false; try { // This is not database-related. This process does some // encryption/decryption and uploads some files up to // Azure blob storage. It doesn't touch the DB. SomeLongRunningProcess(); } catch { // Oops, something bad happened. Let's delete the entities! shouldDeleteEntities = true; } // At this point, both entities are in an Unchanged state, child still // appears in parent.Children, nothing is wrong that I can see. parent.MarkAsDeleted(); child.MarkAsDeleted(); // I've tried MyContext.ApplyChanges here for both entities, no change. // At this point, everything appears to be in the state that // they're supposed to be! try { MyContext.SaveChanges(SaveOptions.AcceptAllChangesAfterSave); } catch { // This exception was thrown and I can't figure out why! } ``` What's wrong with this logic? Why am I not able to simply delete these two records? I've tried calling `MyContext.ApplyChanges` after I call `MarkAsDeleted`. I've tried all sorts of things and no matter what, no matter how hard I try to tell the Context that I want both of them deleted, it keeps throwing this exception.