Entity Framework’s Change Tracking is Jenga for ORM’s

I recently spent an entire evening tracking down a crazy Entity Framework error:

"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."

Oh now that’s a classic Entity Framework error. The problem is that the operation I was performing had no changes to the relationships. I was just setting some data on the object and saving it back. Everything else on the site worked, I could delete, add and update DB objects everywhere, it was just this one page, but why? So began a 6 hour crown and coke fueled debugging session where I had to “git reset” 4 times.

jenga

Entity Framework’s change tracking system is very complicated and far reaching. Operations that you perform on change tracked objects can have a big impact in different areas. MSDN has a good area on Change Tracking for POCO objects to get an overview. And there is a good answer on Stack Overflow on it as well.

I eventually tracked down the offending code, but the kicker, it has been working in the system for over 6 months! So why now all of a sudden did I get this error message? I would say that change tracking is a house of cards and that can be true, one issue or one mistake and it can all fall down, but really Entity Framework and it’s Change Tracking is a game of Jenga, something you did early can cause a catastrophic failure latter on.

Lets look at the offending code, that was almost completely separate from where I was getting the Entity Framework exception above:

public List<DepartmentGroup> GetAllGroupsForDepartment(int departmentId)
{
    List<DepartmentGroup> departmentGroups = GetAllGroupsForDepartmentUnlimited(departmentId);

    // Remove all Disabled or Hidden Users
    foreach (var dg in departmentGroups)
    {
         dg.Members = (from m in dg.Members
                       where !_departmentsService.IsUserDisabled(m.UserId) && !_departmentsService.IsUserHidden(m.UserId)
                       select m).ToList();
    }

    return departmentGroups;
}

The code above get’s all the DepartmentGroup’s for a Department, pretty simple. In Resgrid you can hide or disable users, so they won’t appear in parts of the system. So we remove them from the list of members and continue. This is where the issue lies, because we are removing Members, which was populated by Entity Framework and all change tracked it assumed that we wanted to delete the members that didn’t make back into the Members list. Leaving their DepartmentGroup reference null and causing the error when we tried to submit changes.

This from my experiences with Resgrid which is a cloud service company, SaaS deployed on Microsoft Azure, providing logistics and management tools to first responder organizations like volunteer fire, career fire, EMS, search and rescue, public safety, disaster relief organizations, etc. It was founded in late 2012 by myself and Jason Jarrett (staxmanade).

Like I said before this code has been in the system for 6 months without issue. But why did it break now and how did that code affect a completely unrelated part of the system? Normally in the app, either via the API or the web site, calls like this live in isolation and the context is scoped to the current request, giving us a nice Unit of Work Pattern. Every time that method got called it’s affects on the context were short lived and isolated.

We recently added a new feature to the Edit Profile pages where you could set a person’s group, so to get the departments groups we called that method, in the POST of the controller we re-get them to re-populate the view model. This is where we ran into the issue, change tracking detected the removal and when we tried to update our unrelated object the error was thrown.

What I wish is that Entity Framework would allow us to figure our what SubmitChanges() was trying to do, this would have made the debugging process far easier. Instead it was hours upon hours of work and was only found when I started commenting stuff out and removing it.

So if you are getting the error above and your not removing child objects from the database take a look at other operations your performing in the scope (this works well in your using a Unit of Work pattern). As a practice don’t remove objects from a change tracked collection unless you want to remove the object from the db, instead remove change tracking or spin the result into a new collection.

About: Shawn Jackson

I’ve spent the last 18 years in the world of Information Technology on both the IT and Development sides of the aisle. I’m currently a Software Engineer for Paylocity. In addition to working at Paylocity, I’m also the Founder of Resgrid, a cloud services company dedicated to providing logistics and management solutions to first responder organizations, volunteer and career fire departments, EMS, ambulance services, search and rescue, public safety, HAZMAT and others.