none
An object with the same key already exists in the ObjectStateManager. (REALLY?) RRS feed

  • Question

  • I using EF 4.2 in an MVC3 application.

    I follow the below pattern inside a resposity to save either new photographers or modified ones.  This works in this case.  But a photographer has 0 - many Assignments and Assignments have 0 - many Assignment Statuses. 

    This same pattern works for adding and modifying statuses.  In both cases the Photographer class and PhotographerAssignmentStatus Class have id fields that are mapped by convention to identity columns of the same name in the database.

    This same pattern works for adding and modifying statuses.  In both cases the Photographer class and PhotographerStatus Class have id fields that are mapped by convention to identity columns of the same name in the database.  See data model and the code that follows.

            public void SavePhotographer(Photographer photographer)
            {
                if (photographer.ID == 0)
                {
                    context.Photographers.Add(photographer);
                }
                else
                {
                    context.Entry(photographer).State = EntityState.Modified;
                }
                context.SaveChanges();
            }

    However, this same pattern will not work for saving modification to a PhotographerAssignment instance.  The commented out code is the hack that works around this.  Also adds and deletes work just fine. 

            public void SaveAssignment(PhotographerAssignment assignment)
            {
                try
                {
                    if (context.PhotographerAssignments.ToList().Contains(assignment))
                    {
                        Detach(assignment);
                        context.Entry(assignment).State = EntityState.Modified;
                        //foreach (PhotographerAssignment item in context.PhotographerAssignments)
                        //{
                        //    if (item.PhotographerID == assignment.PhotographerID & item.LabOrderNbr == assignment.LabOrderNbr)
                        //    {
                        //        context.PhotographerAssignments.Remove(item);
                        //        context.PhotographerAssignments.Add(assignment);
                        //    }
                        //}
                    }
                    else
                    {
                        context.PhotographerAssignments.Add(assignment);
                    }
                }
                catch (DataException ex)
                {
                    string msg = ex.Message + ex.InnerException;
                }
                context.SaveChanges();
            }

    Since I don't have an idenity column on PhotographerAssignment, its primary key is {PhotographerID, LabOrderNbr). This is basically an associative table with payload between assignments and events. Therefore, instead of checking whether the PK is 0, I am checking whether the PhotographerAssignments contains the assignment instance. If it does, I want to update the database otherwise I want to insert into the the database.

    However, when I hit this line: context.Entry(assignment).State = EntityState.Modified; I get the dreaded and prolific:

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

    This does not occur with saving photographer or with statuses.  I have seen many of the hundreds posts on this subject but none seem to address my issue any many are out of date.  I am pulling the PhotographerAssignment instance from the context and changing fields in that instance. 

    Here is the code inside my controller

    public class AdminController : Controller
        {
            public int PageSize = 4;
    
            private IPhotographerRepository repository;
            public AdminController(IPhotographerRepository photographerRepository)
            {
                repository = photographerRepository;
            }
    
            public ViewResult Index(int page = 1)
            {
                PhotographersListView viewModel = new PhotographersListView
                {
                    Photographers = repository.Photographers
                    .OrderBy(p => p.LastName)
                    .Skip((page - 1) * PageSize)
                    .Take(PageSize),
                    PagingInfo = new PagingInfo
                    {
                        CurrentPage = page,
                        ItemsPerPage = PageSize,
                        TotalItems = repository.Photographers.Count()
                    }
                };
                return View(viewModel);
            }
    
            public ViewResult ListWithAssignments(Int32? id, string LabOrderNbr = "")
            {
                PhotographerAssignmentView viewModel = new PhotographerAssignmentView();
                viewModel.Photographers = repository.Photographers.OrderBy(p => p.LastName);
    
                if (id != null)
                {
                    ViewBag.PhotographerID = id;
                    viewModel.Assignments = viewModel.Photographers.Where(p => p.ID == id).Single().Assignments;
                }
                
                if (LabOrderNbr != "")
                {
                    ViewBag.LabOrderNbr = LabOrderNbr;
                    viewModel.Statuses = viewModel.Assignments.Where(s => s.PhotographerID == id & s.LabOrderNbr == LabOrderNbr).Single().Statuses;
                }
    
                return View(viewModel);
            }
    
            [HttpGet]
            public ViewResult EditPhotographer(int ID=0)
            {
                Photographer photographer = repository.Photographers.FirstOrDefault(p => p.ID == ID);
                return View(photographer);
            }
    
            [HttpPost]
            public ActionResult EditPhotographer(Photographer Photographer)
            {
                if (ModelState.IsValid)
                {
                    repository.SavePhotographer(Photographer);
                    TempData["message"] = string.Format("{0} has been saved", Photographer.FirstName + " " + Photographer.LastName);
                    return RedirectToAction("ListWithAssignments");
                }
                else
                {
                    // there is something wrong with the data values
                    return View("EditPhotographer", Photographer);
                }
            }
    
            public ViewResult AddPhotographer()
            {
                return View("EditPhotographer", new Photographer());
            }
    
            [HttpGet]
            public ActionResult DeletePhotographer(int ID, bool? saveChangesError)
            {
                if (saveChangesError.GetValueOrDefault())
                {
                    ViewBag.ErrorMessage = "Unable to save changes. Try again, and if the problem persists see your system administrator.";
                }
                Photographer photog = repository.Photographers.FirstOrDefault(p => p.ID == ID);
                return View(photog);
            }
    
            [HttpPost]
            public ActionResult DeletePhotographer(int ID)
            {
    
                try
                {
                   Photographer photog = repository.Photographers.FirstOrDefault(p => p.ID == ID);
                    if (photog != null)
                    {
                        repository.DeletePhotographer(photog);
                        TempData["message"] = string.Format("{0} was deleted", photog.FirstName + " " + photog.LastName);
                    }
                }
                catch (DataException)
                {
                    return RedirectToAction("Delete", new System.Web.Routing.RouteValueDictionary { { "id", ID }, { "saveChangesError", true } });
                }
     
                return RedirectToAction("Index");
            }
    
    
            //Photographer Assignment Methods
    
            public ViewResult AddAssignment()
            {
                return View("EditAssignment", new PhotographerAssignment());
            }
    
            [HttpGet]
            public ViewResult EditAssignment( string LabOrderNbr, int PhotographerID = 0)
            {
               
                PhotographerAssignment assignment = repository.PhotographerAssignments.FirstOrDefault(p => p.PhotographerID == PhotographerID & p.LabOrderNbr == LabOrderNbr);
               
                return View(assignment);
            }
    
            [HttpPost]
            public ActionResult EditAssignment(PhotographerAssignment assignment)
            {
                if (ModelState.IsValid)
                {
                   repository.SaveAssignment(assignment);
                   return RedirectToAction("ListWithAssignments");
                }
                else
                {
                    // there is something wrong with the data values
                    return View("EditAssignment",assignment);
                }
            }
    
            [HttpGet]
            public ActionResult DeleteAssignment(int PhotographerID, string LabOrderNbr, bool? saveChangesError)
            {
                if (saveChangesError.GetValueOrDefault())
                {
                    ViewBag.ErrorMessage = "Unable to save changes. Try again, and if the problem persists see your system administrator.";
                }
                PhotographerAssignment assignment = repository.PhotographerAssignments.FirstOrDefault(p => p.PhotographerID == PhotographerID & p.LabOrderNbr == LabOrderNbr);
                return View(assignment);
            }
    
            [HttpPost]
            public ActionResult DeleteAssignment(int PhotographerID, string LabOrderNbr)
            {
    
                try
                {
                    PhotographerAssignment assignment = repository.PhotographerAssignments.FirstOrDefault(p => p.PhotographerID == PhotographerID & p.LabOrderNbr == LabOrderNbr);
                    if (assignment != null)
                    {
                        repository.DeleteAssignment(assignment);
                        TempData["message"] = string.Format("Event number {0} was deleted", assignment.LabOrderNbr);
                    }
                }
                catch (DataException)
                {
                    //return RedirectToAction("Delete", new System.Web.Routing.RouteValueDictionary { { "PhotographerID", PhotographerID }, {"LabOrderNbr", LabOrderNbr};, { "saveChangesError", true } });
                }
    
                return RedirectToAction("ListWithAssignments");
            }
    
    
            //Assignment Statuses methods
    
            public ViewResult AddStatus()
            {
                return View("EditStatus", new PhotographerAssignmentStatus());
            }
    
            [HttpGet]
            public ViewResult EditStatus(int ID = 0)
            {
    
                PhotographerAssignmentStatus status = repository.PhotographerAssignmentStatuses.FirstOrDefault(s => s.ID == ID);
    
                return View(status);
            }
    
            [HttpPost]
            public ActionResult EditStatus(PhotographerAssignmentStatus status)
            {
                if (ModelState.IsValid)
                {
                    repository.SaveAssignmentStatus(status);
                    return RedirectToAction("ListWithAssignments");
                }
                else
                {
                    // there is something wrong with the data values
                    return View("EditStatus", status);
                }
            }
    
            [HttpGet]
            public ActionResult DeleteStatus(int ID, bool? saveChangesError)
            {
                if (saveChangesError.GetValueOrDefault())
                {
                    ViewBag.ErrorMessage = "Unable to save changes. Try again, and if the problem persists see your system administrator.";
                }
                PhotographerAssignmentStatus status = repository.PhotographerAssignmentStatuses.FirstOrDefault(p => p.ID == ID);
                return View(status);
            }
    
            [HttpPost]
            public ActionResult DeleteStatus(int ID)
            {
    
                try
                {
                    PhotographerAssignmentStatus status = repository.PhotographerAssignmentStatuses.FirstOrDefault(p => p.ID == ID);
                    if (status != null)
                    {
                        repository.DeleteAssignmentStatus(status);
                        TempData["message"] = string.Format("{0} was deleted", status.StatusType);
                    }
                }
                catch (DataException)
                {
                    return RedirectToAction("Delete", new System.Web.Routing.RouteValueDictionary { { "id", ID }, { "saveChangesError", true } });
                }
    
                return RedirectToAction("Index");
            }
    
        }

    Here is repository code:

    public class PhotographerRepository : IPhotographerRepository, IPhotographerAssignment, IAssignmentStatus, IDisposable
        {
            private EFDbContext context = new EFDbContext();
    
            public IQueryable<Photographer> Photographers
            {
                get { return context.Photographers; }
            }
    
            public void SavePhotographer(Photographer photographer)
            {
                if (photographer.ID == 0)
                {
                    context.Photographers.Add(photographer);
                }
                else
                {
                    context.Entry(photographer).State = EntityState.Modified;
                }
                context.SaveChanges();
            }
    
            public void DeletePhotographer(Photographer photographer)
            {
    
                context.Photographers.Remove(photographer);
                context.SaveChanges();
            }
    
            private bool disposed = false;
    
            //Method associated with PhotographerAssignment
            public IQueryable<PhotographerAssignment> PhotographerAssignments
            {
                get { return context.PhotographerAssignments; }
            }
    
            public void SaveAssignment(PhotographerAssignment assignment)
            {
                try
                {
                    if (context.PhotographerAssignments.ToList().Contains(assignment))
                    {
                        //Detach(assignment);
                        context.Entry(assignment).State = EntityState.Modified;
                        //foreach (PhotographerAssignment item in context.PhotographerAssignments)
                        //{
                        //    if (item.PhotographerID == assignment.PhotographerID & item.LabOrderNbr == assignment.LabOrderNbr)
                        //    {
                        //        context.PhotographerAssignments.Remove(item);
                        //        context.PhotographerAssignments.Add(assignment);
                        //    }
                        //}
                    }
                    else
    
                    {
                        context.PhotographerAssignments.Add(assignment);
                    }
                }
                catch (DataException ex)
                {
                    string msg = ex.Message + ex.InnerException;
                }
                context.SaveChanges();
            }
    
            public void DeleteAssignment(PhotographerAssignment assignment)
            {
                context.PhotographerAssignments.Remove(assignment);
                context.SaveChanges();
            }
    
            //methods associated with Assignment Statuses
    
            public IQueryable<PhotographerAssignmentStatus> PhotographerAssignmentStatuses
            {
                get { return context.PhotographerAssignmentStatuses; }
            }
    
    
            public void SaveAssignmentStatus(PhotographerAssignmentStatus status)
            {
                if (status.ID == 0)
                {
                    context.PhotographerAssignmentStatuses.Add(status);
                }
                else
                {
                    context.Entry(status).State = EntityState.Modified;
                }
                context.SaveChanges();
            }
    
            public void DeleteAssignmentStatus(PhotographerAssignmentStatus status)
            {
                context.PhotographerAssignmentStatuses.Remove(status);
                context.SaveChanges();
            }
    
            public void Detach(PhotographerAssignment assignment) { ((IObjectContextAdapter)context).ObjectContext.Detach(assignment); }
    
            protected virtual void Dispose(bool disposing)
            {
                if (!this.disposed)
                {
                    if (disposing)
                    {
                        context.Dispose();
                    }
                }
                this.disposed = true;
            }
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
    
    
        }

    Here is the dbContext:

        public class EFDbContext : DbContext
        {
            public DbSet<Photographer> Photographers { get; set; }
            public DbSet<PhotographerAssignment> PhotographerAssignments { get; set; }
            public DbSet<PhotographerAssignmentStatus> PhotographerAssignmentStatuses { get; set; }
            public DbSet<Event> Events { get; set; }
            public DbSet<PhotoRole> PhotoRoles { get; set; }
    
            public EFDbContext(): base() // since I am not specifying a connection string, defaults to connection string of same name in web.config
            {
                Database.SetInitializer<EFDbContext>(null);
            }
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
                modelBuilder.Configurations.Add(new PhotographerAssignmentConfiguration());
                modelBuilder.Configurations.Add(new EventConfiguration());
                modelBuilder.Configurations.Add(new PhotoRoleConfiguration());
    
            }
        }
    Any help is appreciated. Let me know if you need to see anything else.


    WhiskeyRomeoLima

    Sunday, July 8, 2012 8:52 PM

Answers

  • I have found the problem:

    This code below in the PhotographerRepository class apparently destroys the context between the get and the post.  Commenting out this code and also commenting the IDisposable from the list of interfaces implemented fixed the problem.

    However, the question remains how to control the context so that you get a fresh context when you need it and dispose of it after all operations are completed that need that same contex.

            protected virtual void Dispose(bool disposing)
            {
                if (!this.disposed)
                {
                    if (disposing)
                    {
                        context.Dispose();
                    }
                }
                this.disposed = true;
            }
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }


    WhiskeyRomeoLima

    Wednesday, July 11, 2012 4:33 PM

All replies

  • Hi WhiskeyRomeoLima,

    Welcome to MSDN Forum.

    I will do more research on this issue and come back as soon as possible, thanks for your understanding.

    Best Regards


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us

    Tuesday, July 10, 2012 6:38 AM
    Moderator
  • Hi Allen,

    I appreciate your time on this. I edited this reply to show you the configuration class for this entity.

        class PhotographerAssignmentConfiguration : EntityTypeConfiguration<PhotographerAssignment>
        {
            public PhotographerAssignmentConfiguration()
                : base()
            {
                HasKey(p => new { p.PhotographerID, p.LabOrderNbr });
            }
        }
    }

    Bill


    WhiskeyRomeoLima


    Tuesday, July 10, 2012 1:45 PM
  • I have found the problem:

    This code below in the PhotographerRepository class apparently destroys the context between the get and the post.  Commenting out this code and also commenting the IDisposable from the list of interfaces implemented fixed the problem.

    However, the question remains how to control the context so that you get a fresh context when you need it and dispose of it after all operations are completed that need that same contex.

            protected virtual void Dispose(bool disposing)
            {
                if (!this.disposed)
                {
                    if (disposing)
                    {
                        context.Dispose();
                    }
                }
                this.disposed = true;
            }
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }


    WhiskeyRomeoLima

    Wednesday, July 11, 2012 4:33 PM
  • Hi,

    A bit strange as this is anyway how the web works (no state). So unless you do something, the default is rather that the DbContext should not survive anyway from one http request to the next (or I'm really tired ?)...


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".


    Wednesday, July 11, 2012 5:50 PM
  • Patrice,

    You are right. I also was second guessing my explanation: "This code below in the PhotographerRepository class apparently destroys the context between the get and the post."

    While getting rid of the dispose methods fixed the problem, I am not sure what problem it fixed. This problem did NOT manifest itself while editing Photographers and PhotographerAssignmentStatus which have single column PK's and which happen to be identity columns (that also follow EF's naming convention). The PhotographerAssignment entity has a composite PK composed of foreign keys contributed by Photographer (PhotographerID) and Event (LabOrderNbr).

    I think the composite PK in the PhotographerAssignment is tripping up EF; but I have no factual basis for this. Normally the web is stateless; but, it is possible that EF does manage the context across get and post within the same form. 


    WhiskeyRomeoLima

    Wednesday, July 11, 2012 6:31 PM
  • Not sure how context is instanciated in your app. Do you use dependency injection ? (it could be an inconsistancy in how lifetimes are declared ?)

    For now the only thing I could think off is that the lifetime of the context you expose in PhotographerRepository was longer than the PhotographerRepository instance you used.

    So earlier this instance was dispose and then the context was also disposed. Now this instance doesn't dispose anymore the context that can then shows its longer lifetime ?

    I don't expect EF to do anything special when used from within a web app...


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".

    Thursday, July 12, 2012 8:58 AM
  • Her are the first few lines of the code I initially posted for the AdminController:

            private IPhotographerRepository repository;
    
            public AdminController(IPhotographerRepository photographerRepository)
            {
                repository = photographerRepository;
            }

    Here is the code for Ninject Controller factory. So yes I am using dependency injection via the constructor of the AdminController.

    With the dispose code in the PhotographerRepository, there were no problems with editing Photographer or PhotographerAssignmentStatus as I have earlier stated. So the dispose code caused problems for PhotographerAssignment but not for Photographer, or PhotographerAssignmentStatus. That is puzzling for me.

    namespace PhotographerAssignmentSystem.WebUI.Infrastructure
    {
        public class NinjectControllerFactory : DefaultControllerFactory
        {
            private IKernel ninjectKernel;
            public NinjectControllerFactory()
            {
                ninjectKernel = new StandardKernel();
                AddBindings();
            }
            protected override IController GetControllerInstance(RequestContext requestContext,
            Type controllerType)
            {
                return controllerType == null
                ? null
                : (IController)ninjectKernel.Get(controllerType);
            }
            private void AddBindings()
            {
                ninjectKernel.Bind<IPhotographerRepository>().To<PhotographerRepository>();
                ninjectKernel.Bind<IEventRepository>().To<EventRepository>();
            }
        }
    }


    WhiskeyRomeoLima


    Thursday, July 12, 2012 1:49 PM
  • Hi WhiskeyRomeoLima,

    I have found two articles which introduces how to use Entity Framework in MVC, it declares the details about context dispose and the common solution, please refer them below.

    Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application (9 of 10)

    Advanced Entity Framework Scenarios for an MVC Web Application (10 of 10)

    Best Regards


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us

    Monday, July 16, 2012 2:14 AM
    Moderator