2

I am having problem with saving the changes to database.

I am updating the model A in my controller, however when I save the changes using SaveChanges() I end up having a duplicated item in database for B.

After the UpdateModel() is called I inspected the Bs property and it was as I expected however right after the SaveChanges() is called if I inspect the Bs property I will see that the Id is completely different (new Id and new entry).

My class is similar to this:

public class A
{
    [HiddenInput(DisplayValue = false)]
    public int AId { get; set; }

    public string Name { get; set; }

    public virtual ICollection<B> Bs{ get; set; }
}

public class B
{
    [HiddenInput(DisplayValue = false)]
    public int BId { get; set; }

    public string Name { get; set; }

    public virtual ICollection<A> As{ get; set; }
}

My Controller is like this :

    [HttpPost]
    public ActionResult Edit(A theA)
    {
        try
        {
           db.Entry(theA).State = EntityState.Modified;

           foreach (var item in theA.Bs)
           {
               db.Entry(item).State = EntityState.Modified;
           }

           db.SaveChanges();

           return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }

Am I doing something wrong ?

Thanks in advance

Raha
  • 1,959
  • 3
  • 19
  • 28
  • Your controller method would be important to see where you retrieve the objects from DB, modify them and call SaveChanges. – Slauma Apr 17 '11 at 12:53

1 Answers1

8

That is common behavior. The problem is that EF doesn't know that you attached an existing B so it automatically inserts a new record. You must say EF that the B is existing one by calling:

// here add B to the collection in the A and after that call:
dbContext.Entry<B>(someB).State = EntityState.Unchanged();

or by attaching B before you add it to collection in A (I'm not sure if this is possible when using UpdateModel in ASP.NET MVC).

dbContext.Bs.Attach(someB);
// now add B to the collection in the A

Other possibility is to load B from database first and add loaded object to the collection in A but it is additional roundtrip to database.

int id = someB.Id;
var loadedB = dbCotnext.Bs.Single(b => b.Id == id);
someA.Bs.Add(loadedB);
dbContext.As.Add(someA);
dbContext.SaveChanges();

Conclusion: Every time you call Add the whole object graph is tracked as inserted unless you attach related entities first (before you add them to inserted parent - the 2nd and 3rd example) or unless you manually change the state of related entities back to unchanged after adding the parent. (the 1st example).

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • Thanks, I tried the first method, however I get the following message as soon as its called: "AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges." – Raha Apr 17 '11 at 14:50
  • So how did you get your B instance and how did you create your context? Is the context instanced per request and is B with given key used only once (single instance) per context? – Ladislav Mrnka Apr 17 '11 at 14:54
  • I have added my ActionMethod to the Question, can you please take a look ? – Raha Apr 17 '11 at 17:23
  • I can now edit without any problem by creating a completely new Context in my action method. Is this correct ? Why can't I keep one context for the whole controller ? – Raha Apr 17 '11 at 18:09
  • Have you used your controller context for anything else? Is it per request context or globally shared context? – Ladislav Mrnka Apr 17 '11 at 18:54
  • Before this fix, I was using one context per controller and this didn't work. Then I created a brand new context for my edit action method (inside the method) then this fixed the problem. – Raha Apr 17 '11 at 20:27
  • But one context per controller actually doesn't answer the question. Did you used a new instance of the context for each request? – Ladislav Mrnka Apr 17 '11 at 21:59
  • @Ladislav yes I used a new instance context for each request. – Raha Apr 19 '11 at 06:41