Question

I have to entities which have many-to-many relationship.

public class M1 { M1Id int; ICollection<M2> M2s { get; set; } }
public class M2 { M2Id int; ICollection<M1> M1s { get; set; } }
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> {
    public DbSet<M1> M1s{ get; set; }
    public DbSet<M2> M2s{ get; set; }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<M1>().HasMany<M2>(e => e.M2s).WithMany(c => c.M1s)
            .Map(c =>
            {
                c.MapLeftKey("M1"); 
                c.MapRightKey("M2Id"); 
                c.ToTable("M1AndM2");
            });

And I need to create an M1 from VM in the post method.

var M1 = new M1
{
    Id = M1Vm.M1Id,
    // ....
    M2s = db.M1.FirstOrDefault(x => x.M1Id == M1Vm.M1Id).M2s
    if (ModelState.IsValid)
    {
        db.Entry(M1).State = EntityState.Modified; // Error
        UpdateM2s(M1, M1Vm.NewM2s); // Update table M1AndM2

It will raise the following error when set the State of the M1.

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.

The UpdateM2s will run M1.M2s.Add(m2) or M1.M2s.Remove(m2) after the comparison. The following is the code.

    private void UpdateM2s(M1 m1, IEnumerable<NewM2s> newM2s)
    {
        foreach (var newM2 in newM2s)
        {
            // Add many-to-many relationship
            if (newM2.Assigned && !m1.M2s.Any(c => c.M2Id == newM2.M2Id))
            {
                var m2 = new M2 { M2Id = newM2.M2Id, .... };
                db.M2.Attach(m2);
                m1.M2s.Add(m2);
            }
            // Remove many-to-many relationship
            else if (!newM2.Assigned && m1.M2s.Any(c => c.Id == assigned.Id))
            {
                var m2 = new M2 { M2Id = newM2.M2Id, .... };
                db.M2.Attach(m2); // Same Error
                m1.M2s.Remove(m2);
            }
        }
    }
Était-ce utile?

La solution

The line...

M2s = db.M1.FirstOrDefault(x => x.M1Id == M1Vm.M1Id).M2s

...loads the M1 entity with key M1Vm.M1Id from the database and attaches it to the context. Then it loads its M2s collection via lazy loading. At the same time you are creating a new entity M1 with the same key Id = M1Vm.M1Id and attaching it to the context by setting its state with db.Entry(M1).State = EntityState.Modified. So, you have two objects with the same key attached to the context which is what the exception is complaining about.

You could try to fix the problem by loading the M2s only, without the parent:

M2s = db.M1.Select(m1 => m1.M2s).FirstOrDefault(x => x.M1Id == M1Vm.M1Id)

However, I believe the better approach would be to not create a new object at all, but load the original M1 from the database including the M2s collection and then update the object graph with the view model:

if (ModelState.IsValid)
{
    var m1 = db.M1.Include(m => m.M2s).FirstOrDefault(x => x.M1Id == M1Vm.M1Id);
    db.Entry(m1).CurrentValues.SetValues(M1Vm);
    UpdateM2(m1, M1Vm.NewM2s);
    db.SaveChanges();
}

Edit

Now that I see the UpdateM2 method I suggest that you change that as well. In the else part you are creating a new M2 with the same key as another m2 that already has been included and attached in the query for m1. Hence you get the same exception about two attached objects with the same key again, this time just refering to M2 and not M1. You can try to change the else block like so:

else if (!newM2.Assigned)
{
    var m2 = m1.M2s.SingleOrDefault(c => c.Id == newM2.Id);
    if (m2 != null)
    {
        m1.M2s.Remove(m2);
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top