Code first - how can I save an ICollection when I&

2020-06-23 05:52发布

问题:

If I have the below class:

public class Foo()
{
public int PropertyIWantUpdated {get; set;}
public int PropertyIDontWantUpdated (get; set}

public ICollection<Bar> Bars {get; set;}
}

When saving to my db, instead of

context.Entry(thisFoo).State = EntityState.Modified;

I'm using

context.Entry(thisFood).Property(tf => tf.PropertyIWantUpdated).IsModified = true;

how can I also save changes to Bars?

回答1:

It depends what you are trying to update. First of all let me clarify one important fact - if you have detached entity graph (more entities with relation) and you want to pass all changes to EF you are responsible for telling EF what has changed in every entity and every relation - EF will not help you with it.

If you are trying to update just Bar instances and you didn't change relations (= you didn't add new Bar to Foo or removed Bar from Foo) you just need to iterate Bars and set them to Modified state.

If you also changed content of Bars collection the whole process become really complicated and the approach depends on the way how you defined your entities = If you are using independent or foreign key association.

In case of foreign key association (Bar has FK as property = in Bar you have something like FooId) you follows similar approach as at the beginning. You iterate Bars and set state to:

  • Modified if an existing Bar was assigned to Foo
  • Added if a new Bar was assigned to Foo

There is one big issue. If you removed some Bar instances from Bars collection you must also attach them to the context and set their state accordingly:

  • Modified if FK should be set to null
  • Deleted if Bar should be deleted

This all is only for one-to-many relations.

If you thought that previous approach was complicated be prepared that in case of independent association (Bar doesn't have FK property - always the case for many to many relations) the process is even worse. Independent associations have their own object tracking the state = setting the state on Bar entity doesn't persist new relation. The first problem is that this object is not directly accessible from DbContext API - you must convert DbContext to ObjectContext and use ObjectStateManager to get the access to ObjectStateEntry representing the relation. After that you must correctly set its state which is not as easy as it looks like because relation cannot be in Modified state - it can be only in Unchanged, Added or Deleted. It means that if you changed the Bar's relation from one to another Foo you must first find the old relation and set it as deleted and then you can set the new relation as added. If you have many-to-many relation and you also want to add, delete and update related objects (not only relations) this can be really "big fun" - especially the fact that you must somewhere keep the information what has changed to be able to set all states correctly.

More discussion about this problem (global in EF) is here - it is not related to the DbContext API but because the new API is just wrapper of the old ObjectContext API same problems remain.

Do you think it is feasible? I don't think so. Because of that you should try to avoid this. There are some ways how to avoid it:

  • Make changes on attached object graph - it means that you will first attach the original state of the entity graph to the context and then you will do all your changes.
  • Load the original object graph and manually merge all changes from the new graph to the loaded (and attached) one.
  • In case of ObjectContext API you can use Self-tracking entities which are able to track the state and automatically set everything with applied to the context. They have some other disadvantages and limitations (for example they don't support lazy loading) and they are not available for DbContext API.