A standard pattern when applying updates to disconnected entities is as follows:
- Attach the root entity to the context to enable change tracking across the graph
- This marks the entire object graph as
EntityState.Unchanged
, hence you need to walk the graph and set the state accordinly
- Mark the parent entity as
EntityState.Modified
so that its changes are persisted
- For each child entity, work out the nature of the changes (inserts, deletions or updates) and mark their state accordingly
- When the context is saved, the changes across the graph will be persisted.
Adopting this approach means you can reduce your dependency requirements down to a single repository for the root entity.
For example, and assuming you are only dealing with updates:
using (var context = new MyContext())
{
context.attach(parentEntity);
context.Entry(parentEntity).State = EntityState.Modified;
context.Entity(parentEntity.ChildEntity1).State = EntityState.Modified;
context.Entity(parentEntity.ChildEntity2).State = EntityState.Modidied;
context.SaveChanges();
}
This is often encapulated in an AttachAsModified method on your repositories, which knows how to "paint the state" of an object graph based on the root entity of the graph.
E.g.
public class MyRepository<TEntity>
{
public void AttachAsModified(TEntity entity)
{
_context.attach(entity);
_context.Entry(entity).State = EntityState.Modifed;
_context.Entity(entity.ChildEntity1).State = EntityState.Modified;
// etc
_context.SaveChanges();
}
}
There is additional complexity if you need to consider inserts or deletes of child entities. These boil down to loading the current state of the root entity and its children and then comparing the child sets to the sets on the updated root entity. The state is then set to EntityState.Deleted
or EntityState.Added
depending on the overlap of the sets.
NB code typed straight into the browser so there may/will be some typos.