keysersoze
keysersoze

Reputation: 777

Adding and deleting many-to-many using DbContext API

I am using Entity Framework and DbContext API do build my application but I am having trouble working with objects with many-to-many relations. A simplified save-method could look like this

public void MyObj_Save(MyObj myobj)
{
  DbContext.Entry(myobj).State = EntityState.Added;
  DbContext.SaveChanges();
}

This code works fine, but if MyObj contains a many-to-many relation this is not saved. I know from using the old POCO API, that I needed to attach the related objects to the context but I cannot find a way to do this correctly with the DbContext API - a simplified example below

public void MyObj_Save(MyObj myobj, List<OtherObj> otherObjList)
{
  foreach (OtherObj otherObj in otherObjList)
  {
    DbContext.OtherObj.Attach(otherObj);
    myobj.OtherObj.Add(otherObj);
  }

  DbContext.Entry(myobj).State = EntityState.Added;
  DbContext.SaveChanges();
}

I get no error, but the relations are not saved. What to do?

Upvotes: 3

Views: 3090

Answers (2)

keysersoze
keysersoze

Reputation: 777

public void MyObj_Save(MyObj myobj, List<OtherObj> otherObjList)
{
    DbContext.Entry(myobj).State = EntityState.Added;

    foreach (OtherObj otherObj in otherObjList)
    {
        (((System.Data.Entity.Infrastructure.IObjectContextAdapter)DbContext)
            .ObjectContext)
            .ObjectStateManager
            .ChangeRelationshipState(myobj, otherObj,
                q => q.OtherObjs, EntityState.Added);
    }

    DbContext.SaveChanges();
}

Again, it is a simplified and not a real life example!

Upvotes: 2

Slauma
Slauma

Reputation: 177133

I quote your (important!) comment:

The objects I send to the method are attached and EntityState is Unchanged. The configuration of my DbContext is, that I have disabled AutoDetectChangesEnabled...

So, your code would look like this:

DbContext.Configuration.AutoDetectChangesEnabled = false;

DbContext.Entry(myobj).State = EntityState.Unchanged;
foreach (OtherObj otherObj in otherObjList)
    DbContext.Entry(otherObj).State = EntityState.Unchanged;

// entering MyObj_Save method here
foreach (OtherObj otherObj in otherObjList)
{
    //DbContext.OtherObj.Attach(otherObj); // does not have an effect
    myobj.OtherObj.Add(otherObj);
}

DbContext.Entry(myobj).State = EntityState.Added;
DbContext.SaveChanges();

And this indeed doesn't work because EF doesn't notice that you have changed the relationship between myobj and the list of OtherObj in the line myobj.OtherObj.Add(otherObj); because you have disabled automatic change detection. So, no entries will be written into the join table. Only myobj itself will be saved.

You cannot set any state on an entity to put the state manager into a state that the relationship is saved because it is not an entity state which is important here but a relationship state. These are separate entries in the object state manager which are created and maintained by change detection.

I see three solution:

  • Set DbContext.Configuration.AutoDetectChangesEnabled = true;

  • Call DetectChanges manually:

    //...
    DbContext.Entry(myobj).State = EntityState.Added;
    DbContext.ChangeTracker.DetectChanges();
    DbContext.SaveChanges();
    
  • Detach the new myobj from the context before you set it into Added state (this feels very hacky to me):

    // entering MyObj_Save method here
    DbContext.Entry(myobj).State = EntityState.Detached;
    foreach (OtherObj otherObj in otherObjList)
    //...
    

Maybe it is possible - by getting to the ObjectContext through the IObjectContextAdapter - to modify the relationship entries in the object state manager manually but I don't know how.

In my opinion, this procedure to manipulate entity (and relationship) states manually is not the way you are supposed to work with EF. AutoDetectChangesEnabled has been introduced to make working with EF easier and safer and the only recommended situation to disable it is a high performance requirement (for example for bulk inserts). If you disable automatic change detection without need you are running into problems like this which are difficult to detect and it requires advanced knowledge of EF's inner workings to fix those bugs.

Upvotes: 3

Related Questions