共用方式為


Deleting Foreign-Key Relationships in EF4

I try to keep up with blog posts on the net which involve the entity framework, and this afternoon I came across this post where someone had been experimenting with EF4 and encountered some cases where things didn’t behave as they expected.  I started to respond in a comment to that blog but the comment starting getting awfully long.  So here’s a more complete response (which I’ll then refer to in a short comment).  Please take a moment now to go read the other post for context.  OK.  Now that you are back, here’s my response:

When you use FK-relationships (the default with EF4), there are 3 main kinds of configurations you can encounter which each have slightly different behaviors.  Let me see if I can explain so that you can understand better why the EF behaves the way it does.

  1. The first case is actually what appears in the first and last examples.  It's where the relationship is backed by a foreign key property on one of the entities, and where that foreign key is non-nullable but also not part of the primary key of the entity.  In this case, if you delete the "dependent" entity (namely the one with the FK property--typically on the many side of a 1-many relationship), then the EF knows that the entity going away means the relationship must go away as well.  If, however, you just remove the relationship (either by removing the entity from a collection on the other side or by clearing a reference navigation property or something like that), then the EF attempts to make the foreign key property null which is what will remove the relationship without removing the entity.  The problem is that since the FK property is non-nullable, when you attempt to save changes, the EF will throw an exception.  The EF will allow you to be temporarily in this state which makes it easier to write code where you want to switch an entity to be related to some other entity, but it won't allow you to save when you are in that state.  As a side note, the EF actually goes to significant lengths to support this temporary “conceptual null” so that it’s more convenient to program against entities with non-nullable foreign keys. 

    This kind of configuration is typically used to model scenarios where for a particular entity a relationship is required (you can’t have an order without a customer or something like that), and the EF's approach is to say that removing the relationship without deleting the entity is an error in your business logic.

  2. The second configuration is a simple variation on the first one where everything is the same except that the foreign-key *is* nullable.  In this case, when you remove the relationship, the foreign key property is set to null, and when you save everything works fine.  This configuration didn’t come up in the samples mentioned in the blog post, but it is something we encounter periodically.  Typically you would use something like this to model an optional relationship where an entity might have a related entity or might not—for example, I might have a salesperson for an order or maybe the order came in over the internet and there’s no salesperson. 

  3. Finally, there’s the case where the foreign-key property on one entity is also part of that entity’s primary key.  In this case the entity’s identity is fundamentally dependent on the relationship.  If you remove the relationship, then the entity must be removed.  If you change the relationship to some other entity, then you still have to remove the entity and then insert a new one because the entity with a particular primary key went away and a new entity which may be mostly the same (but which has a different primary key so it’s a different entity) has been added.  So when you have this kind of case, if you remove the relationship then the EF will remove the entity.  This scenario is typically used to model a containment sort of relationship—like the order details lines in an order.  If you remove the relationship between an order and its order detail, then the order detail line should go away.  It’s also true, by the way, that this configuration implies cascade delete behavior from the principal entity to the dependent entity (or entities).  So if you delete the order, then all the order detail entities associated with it will also be deleted.

Another variation to be aware of here in the first two cases is that you can have cascade delete behavior turned on.  If you do, then deleting the principal entity will cause the dependent entity to also be deleted.  Cascade delete does NOT, however, affect what happens when you remove the relationship.  The only case in current releases of the EF where removing the relationship will cause the dependent entity to be removed is #3 above where the primary key of the dependent includes a foreign-key to the principal.  An additional option we’d like to add for a future release of the EF is the ability to opt-in to an auto-delete orphans or cascade relationship or whatever you might want to call it behavior where case #1 above could have an additional option turned on so that it would delete the dependent rather than throwing an exception, but for now this is logic you will have to write yourself.

- Danny

Comments

  • Anonymous
    April 20, 2010
    Hi I am having some trouble understanding the State of an entity I have User [1 to many relation] Address so I do, using(MyContext c = new MyContext()) User u = c.Users.FirstorDefault(); u.FirstName = "Updated"; c.SaveChanged(); Why is "u" not in updated State. Also, when i do, u.addresses.Removeat(0); the address is not in deleted state. However when i try saving I get an error. [http://mocella.blogspot.com/2010/01/entity-framework-v4-object-graph.html] after reading this article i understand why i get the error, however i expected address to be in deleted state. Could you please clarify

  • Anonymous
    April 20, 2010
    For your first case, u will be in the updated state if you check before the call to SaveChanges, but after you call SaveChanges it will be back to the unchanged state--the state just reflects work left to do rather than work that has been done and the like. As far as your second question is concerned, the article explains the three possible configurations in EF4 and the way they behave.  I think what you are expecting would be the behavior of a fourth configuration we have discussed but not yet implemented.  The idea is to set things up so that if a foreign key relationship is removed and the property is not nullable, then the dependent entity would automatically be marked for deletion.  It's my hope we'll be able to add this behavior in a future release of the EF, but for now you just have to remove both the relationship and the entity yourself. If you wanted, you could add a handler for the SavingChanges event, and in that handler you could find appropriate relationship entries marked for deletion and delete the dependent entities--this would simulate the feature.

  • Danny
  • Anonymous
    April 21, 2010
    Hi Thanks for your prompt response. Let me clarify my question. You mentioned, "For your first case, u will be in the updated state if you check before the call to SaveChanges, but after you call SaveChanges it will be back to the unchanged state" I have an event handler registered for SaveChanges on ObjectContext. when i do User u = c.Users.FirstorDefault(); u.FirstName = "Updated"; c.SaveChanges(); The SaveChanges event is invoked. I say IEnumerable<ObjectStateEntry> entries = UnitOfWorkScope.CurrentObjectContext.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added |System.Data.EntityState.Deleted |System.Data.EntityState.Modified) I expect the entries count to be one. But i dont see any entries. You also mentioned "If you wanted, you could add a handler for the SavingChanges event, and in that handler you could find appropriate relationship entries marked for deletion and delete the dependent entities--this would simulate the feature." when i do u.addresses.Removeat(0) I was expecting the objectstatemanager to return me the address entity in Deleted state on the SaveChanges event. That doesnt happen either

  • Anonymous
    April 21, 2010
    Does the user name actually get updated in the database?  If so, then some context definitely has the user in modified state, because the EF uses the context to figure out what it needs to do.  Is there any chance that you SavingChanges event handler is somehow getting a different instance of the context? When it comes to the relationship piece, I don't think you should be seeing a state entry for the address entity in deleted state, but you should see a state entry for the relationship between the two entities in the deleted state.  If you aren't getting any state entries back, then maybe the source of the issue is the same problem above. It's hard to tell without seeing more of your code just what's going on, but I can tell you that the EF definitely will have state entries in the state manager for any changes you make to entities connected to that particular state manager.

  • Danny
  • Anonymous
    April 21, 2010
    yes the data is updated to the database and no, i dont see any relationshipentry in the objectstatemanager.I also sent you some code to your MS email.

  • Anonymous
    April 21, 2010
    The comment has been removed

  • Anonymous
    April 21, 2010
    I hadn't had a chance to look at your code yet, but this provides the clue we need.  The SavingChanges event fires as the VERY FIRST thing when you call SaveChanges.  After this event is fired, the system will call DetectChanges for you before actually persisting things, but if you are using POCO entities which don't have change tracking proxies, then you will need to call DetectChanges yourself if you want to see the changes in the SavingChanges event.

  • Danny
  • Anonymous
    April 21, 2010
    hmm. Thanks for the information

  • Anonymous
    July 14, 2010
    Ok. but how are we going to use the framework with controls like the DataGrid etc that call remove on ICollection (which EntityCollection implements, or the list it gets from GetList) and expects the entity to disappear? DataGrid is not the only one. I have created many of my own generic controls that rely on the behavior of ICollection. Is every object in the graph supposed to be database aware and maintain a reference to the context so that it can properly delete itself every time the Object Oriented logic tells it to be deleted or delete one of its children?

  • Anonymous
    January 24, 2011
    Danny wrote: If you wanted, you could add a handler for the SavingChanges event, and in that handler you could find appropriate relationship entries marked for deletion and delete the dependent entities--this would simulate the feature. How would you do that? When I delete an entity from an entitycollection there is no entries with the deleted state, but an entry with the modified state. The modified property is the FK field. How do I see that a given property is a FK field and how do I know that the entity is removed from a collection and therefor should be deleted? I'm experiencing this problem when the data is being edited in a grid, so calling ObjectContext.DeleteObject is not an option. Please help.

  • Anonymous
    January 26, 2011
    Michael, I suspect this question is a bit beyond what I can answer in a quick blog comment response, but let me try to give you a pointer or to, and then I'll have to refer you to the EF MSDN forums or some other channel where you are likely to get more complete support (especially given the fact that I'm no longer on the EF team and so tend to be distracted by other demands on my time). The general idea is this:

  1. Whenever an ObjectContext instance is created add an event handler for the SavingChanges event.  If you are using the standard code generation, you can do this by implementing the partial method that gets called from the context constructor--I think it's called OnContextCreated or something like that.  You use this mechanism so that the event will always be there when SaveChanges is called regardless of how you are modifying the data along the way.
  2. In this event handler, you will access the ObjectStateManager on the context and retrieve ObjectStateEntries that match criteria for the situation you need to fix up.  You can get an idea for some of this code based on the comments from solipsist above.  You might also want to look at this old blog post: blogs.msdn.com/.../concepts-part-ii-relationships.aspx for some conceptual information about relationships in the object state manager.  Things are a bit different if you are using FK relationships (which didn't exist in the EF back when that was written), but it should give you some general ideas.
  3. If you find relationships that have been deleted but the dependent entity has not, then you can call a method on the object context to delete the entity. This won't happen at the moment when you edit the data in your grid, but it will happen when SaveChanges is called to persist the edits you have made. Hope this helps, Danny
  • Anonymous
    March 26, 2013
    I realise this is an old blog. I'm using the EF5 POCO template, and running into similar issues trying to delete an entity. Say I've retrieved a Customer and its related Orders. As explained above, Customer.Orders.Remove(...) won't work, and throws an exception. I've also tried "context.Entry(order).State = EntitySate.Deleted" and "context.Orders.Remove(order)", but I get the same error. Any ideas what I need to do?

  • Anonymous
    March 26, 2014
    For the thrid kind of configuration, how can I make composite key with inherited properties ?