0

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);
    }
Dan
  • 10,523
  • 17
  • 77
  • 117
  • What is the code the precedes the line `service.Entry(Book).State = EntityState.Modified;`? – Charlino Apr 12 '11 at 16:42
  • I have edited the question to include the code you are asking for. – Dan Apr 12 '11 at 16:51
  • What happens when you take that line out? Does it not save the changes? I would've thought that the context could track the changes without you needing to manually change the EntityState. – Charlino Apr 12 '11 at 17:36
  • @Carlino, exactly, id does not save changes. As I mentioned, the code is the one generated by Mvc Scaffolding, works, and I tried to modify it in the least possible way. One thing I noticed is that in my fix internalBook is actually a proxy, while book that I recieve as parameter is not. – Dan Apr 12 '11 at 17:38

2 Answers2

1

Actually you don't need to query the Book, you can just use these two lines:

service.Entry(book).State = EntityState.Modified;
service.SaveChanges();

So this is the complete code:

[HttpPost]
public ActionResult Edit(Book book)
{    
    if (ModelState.IsValid)    
    {                     
        service.Entry(book).State = EntityState.Modified;
        service.SaveChanges();        

        return RedirectToAction("Index");    
    }    

    return View(book);
}

You can download a complete solution from this post: http://blog.jorgef.net/2011/04/ef-poco-proxies-in-mvc.html

Jorge Fioranelli
  • 449
  • 5
  • 12
0

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);
}
Ladislav Mrnka
  • 349,807
  • 56
  • 643
  • 654
  • which is similar to what I did... But I guess scaffolded code initializes new context with each request? Like this: private BooksAndAuthorsWebContext context = new BooksAndAuthorsWebContext(); This means that new Controller is instantited with each request.. – Dan Apr 12 '11 at 18:01
  • 1
    Context must be initialized with each request. If you are not creating new context for each request you will have serious problems: http://stackoverflow.com/questions/3653009/entity-framework-and-connection-pooling/3653392#3653392 – Ladislav Mrnka Apr 12 '11 at 18:04
  • I am using Ninject. If I understand correctly, InRequestScope should do the trick – Dan Apr 12 '11 at 19:39