I coded my entities with and created DbContext. Then I used MVC Scaffolding to create simple CRUD form for one entity. So far so good, it works as advertised. Now, I decided to replace scaffolding-generated DbContext with simple Service wrapper over DbContext. All it does is delegate to DbContext.
However, now I have the problem on the following line when tried to edit the entity:
service.Entry(Book).State = EntityState.Modified;
“An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key”
I managed to resolve it like this:
PropertyInfo[] infos = typeof(Book).GetProperties();
foreach (PropertyInfo info in infos)
{
info.SetValue(internalBook, info.GetValue(book, null), null);
}
Basically, I get the entity again and copy properties from entity that was handed to me by View. I have also noted that when I obtain the entity, it is proxy, and the one handed to me is not.
What could be the problem?
Here is my service class:
public class BookService
{
private DbContext context;
private DbSet<Book> set;
public BookService(DbContext context, DbSet<Book> set) {
this.context = context;
this.set = set;
}
public IQueryable<Book> Query
{
get { return set; }
}
public virtual void Add(Book entity)
{
set.Add(entity);
}
public virtual void Remove(Book entity)
{
set.Remove(entity);
}
public virtual void SaveChanges() {
context.SaveChanges();
}
public List<Book> All() {
List<Book> books = set.ToList();
return books;
}
public DbEntityEntry<Book> Entry(Book book) {
return context.Entry(book);
}
}
Here is the Edit action Controller code. I have commented the original, scaffolding-generated code:
[HttpPost]
public ActionResult Edit(Book book)
{
Book internalBook = service.Query.Single(b => b.Id == book.Id);
if (ModelState.IsValid)
{
PropertyInfo[] infos = typeof(Book).GetProperties();
foreach (PropertyInfo info in infos)
{
info.SetValue(internalBook, info.GetValue(book, null), null);
}
service.Entry(internalBook).State = EntityState.Modified;
service.SaveChanges();
//context.Entry(book).State = EntityState.Modified;
//context.SaveChanges();
return RedirectToAction("Index");
}
return View(book);
}
You can't attach the book because you have loaded it in the same context. General approach is this:
[HttpPost]
public ActionResult Edit(Book book)
{
Book internalBook = service.Query.Single(b => b.Id == book.Id);
if (ModelState.IsValid)
{
service.Entry(internalBook).CurrentValues.SetValues(book);
service.SaveChanges();
return RedirectToAction("Index");
}
return View(book);
}