none
Entity Framework SaveChanges not saving updated data RRS feed

  • Question

  • I am building a sample MVC project that uses EF. SaveChanges is working fine for adds and deletes, but does not persist changes to column data, and I am not sure why. For example, changing a Product.Name from, say, "Kayak" to "Green Kayak" looks fine when debugging and setting a breakpoint on the SaveProduct method in the repository. SaveChanges returns without error, but the name change does not persist in the database. OTOH, if I ADD a new product and the productID == 0, the new product is successfully added using the same repository method.

    The implementation stubs are as follows:

    REPOSITORY:

    namespace SportsStore.Domain.Concrete
    {
        public class EFProductRepository : IProductRepository
        {
            private EFDbContext context = new EFDbContext();
    
            public IQueryable<Product> Products { get { return context.Products; } }
    
            public void SaveProduct(Product product)
            {
                if (product.ProductID == 0)
                    context.Products.Add(product);
                context.SaveChanges();
            }
    
            public void DeleteProduct(Product product)
            {
                context.Products.Remove(product);
                context.SaveChanges();
            }
        }
    }

    CONTROLLER:

    (NOTE: Both Edit (update) and Create (Add) methods use the Edit view. New products save fine to the DB (propertyID == 0) in the repository, but changes (updates) to existing products don't persist.)

    namespace SportsStore.WebUI.Controllers
    {
        public class AdminController : Controller
        {
            private IProductRepository repository;
    
            public AdminController(IProductRepository repository)
            {
                this.repository = repository;
            }
    
            public ViewResult Index()
            {
                return View(repository.Products);
            }
    
            public ViewResult Edit(int productId)
            {
                var product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
                return View(product);
            }
    
            [HttpPost]
            public ActionResult Edit(Product product)
            {
                if (ModelState.IsValid)
                {
                    repository.SaveProduct(product);
                    TempData["message"] = string.Format("{0} has been saved", product.Name);
                    return RedirectToAction("Index");
                }
                else
                    return View(product);
            }
    
            public ViewResult Create()
            {
                return View("Edit", new Product());
            }
    
            [HttpPost]
            public ActionResult Delete(int productId)
            {
                var product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
                if (product != null)
                {
                    repository.DeleteProduct(product);
                    TempData["message"] = string.Format("{0} has been deleted", product.Name);
                }
                else
                    TempData["message"] = string.Format("{0} not found", product.Name);
    
                return RedirectToAction("Index");
            }
        }
    }

    VIEW:

    @model SportsStore.Domain.Entities.Product
    
    @{
        ViewBag.Title = "Admin: Edit";
        Layout = "~/Views/Shared/_AdminLayout.cshtml";
    }
    
    <h1>Edit @Model.Name</h1>
    
    @using (Html.BeginForm("Edit", "Admin")) { 
        @Html.EditorForModel()
        <input type="submit" value="Save" />
        @Html.ActionLink("Cancel and return to list", "Index")
    }

    ENTITY:

    namespace SportsStore.Domain.Entities
    {
        public class Product
        {
            [HiddenInput(DisplayValue=false)]
            public int ProductID { get; set; }
    
            [Required(ErrorMessage = "Please enter a product name")]
            public string Name { get; set; }
    
            [Required(ErrorMessage = "Please enter a product description")]
            [DataType(DataType.MultilineText)]
            public string Description { get; set; }
    
            [Required]
            [Range(0.01, double.MaxValue, ErrorMessage="Please enter a positive price")]
            public decimal Price { get; set; }
    
            [Required(ErrorMessage="Please specify a category")]
            public string Category { get; set; }
        }
    }





    • Edited by Bill2010 Thursday, May 3, 2012 8:56 PM
    Thursday, May 3, 2012 8:18 PM

Answers

  • First off they are objects with properties not columns. You deal with
    columns using T-SQL which is generated by EF to be submitted to a
    database. But what you are dealing with are properties of an object.
     
    I suspect that during the update. You never got the object in context,
    make changes to the object and tried to save the object within the same
    context. You have a detached object. To update and existing object, it
    has to be retrieved in a context so that it is attached in context, a
    property is changed, EF knows a object's/entity's property has changed,
    the object is marked dirty and EF will save the object.
      As an example....
     
    var context = new DBProduct();
     
    var product = (from a in context.Product where ProdID ==
    2).SingleOrDefault();
     product.Name = "help"
     
    context.Save(product );
     
    context.Dispose();
     
    The above is getting the object in context, make changes to the object
    in context and save the changes with the object in context.
     
    Here is another example where you attach an existing object for update
    to a context. It's doing it for a delete, but you can also to it for an
    update.
     
    ctx.AttachTo("Products", product); //you are attaching an existing
    object update that you retrieved earlier in code which was in a detached
    state.
     
    product.ID = savedID;  //you are forcing a change to the object in an
    attached state. You saved the ID at some point, and you are using the
    same ID, but you have raised the event in the object to indicate that it
    is dirty. You made a change in the object.
     
    You could change all the properties of product and left product.ID alone.
     
    ctx.SaveChanges();
     
     
    • Marked as answer by Bill2010 Friday, May 4, 2012 3:55 PM
    Thursday, May 3, 2012 9:10 PM
  • I made the following change to the SaveProperty method, which works but does not seem pure to me. It does appear that the problem is because it is a detached object.

     public void SaveProduct(Product product)
     {
         context.Entry(product).State = product.ProductID == 0 ? EntityState.Added : EntityState.Modified;
         context.SaveChanges();
     }

    • Marked as answer by Bill2010 Friday, May 4, 2012 3:55 PM
    Thursday, May 3, 2012 9:51 PM

All replies

  • First off they are objects with properties not columns. You deal with
    columns using T-SQL which is generated by EF to be submitted to a
    database. But what you are dealing with are properties of an object.
     
    I suspect that during the update. You never got the object in context,
    make changes to the object and tried to save the object within the same
    context. You have a detached object. To update and existing object, it
    has to be retrieved in a context so that it is attached in context, a
    property is changed, EF knows a object's/entity's property has changed,
    the object is marked dirty and EF will save the object.
      As an example....
     
    var context = new DBProduct();
     
    var product = (from a in context.Product where ProdID ==
    2).SingleOrDefault();
     product.Name = "help"
     
    context.Save(product );
     
    context.Dispose();
     
    The above is getting the object in context, make changes to the object
    in context and save the changes with the object in context.
     
    Here is another example where you attach an existing object for update
    to a context. It's doing it for a delete, but you can also to it for an
    update.
     
    ctx.AttachTo("Products", product); //you are attaching an existing
    object update that you retrieved earlier in code which was in a detached
    state.
     
    product.ID = savedID;  //you are forcing a change to the object in an
    attached state. You saved the ID at some point, and you are using the
    same ID, but you have raised the event in the object to indicate that it
    is dirty. You made a change in the object.
     
    You could change all the properties of product and left product.ID alone.
     
    ctx.SaveChanges();
     
     
    • Marked as answer by Bill2010 Friday, May 4, 2012 3:55 PM
    Thursday, May 3, 2012 9:10 PM
  • I made the following change to the SaveProperty method, which works but does not seem pure to me. It does appear that the problem is because it is a detached object.

     public void SaveProduct(Product product)
     {
         context.Entry(product).State = product.ProductID == 0 ? EntityState.Added : EntityState.Modified;
         context.SaveChanges();
     }

    • Marked as answer by Bill2010 Friday, May 4, 2012 3:55 PM
    Thursday, May 3, 2012 9:51 PM
  • On 5/3/2012 5:51 PM, Bill2010 wrote:
    > I made the following change to the SaveProperty method, which works but
    > does not seem pure to me. It does appear that the problem is because it
    > is a detached object.
    >
    >   public void SaveProduct(Product product)
    >   {
    >       context.Entry(product).State = product.ProductID == 0 ? EntityState.Added : EntityState.Modified;
    >       context.SaveChanges();
    >   }
    >
    >
     
    Pure? As long as it works is the bottom line. :)
     
    Thursday, May 3, 2012 10:17 PM

  • Pure? As long as it works is the bottom line. :)
     
    This will force an update if the object is modified or not. Probably no big deal, but I was hoping there would be some mechanism for creating a snapshot image of the original and only update after comparing the before/after.
    • Edited by Bill2010 Thursday, May 3, 2012 10:25 PM
    Thursday, May 3, 2012 10:24 PM