Answered by:
Entity Framework SaveChanges not saving updated data

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 withcolumns using T-SQL which is generated by EF to be submitted to adatabase. 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 samecontext. You have a detached object. To update and existing object, ithas to be retrieved in a context so that it is attached in context, aproperty 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 objectin context and save the changes with the object in context.http://blogs.msdn.com/b/alexj/archive/2009/03/27/tip-9-deleting-an-object-without-retrieving-it.aspxHere is another example where you attach an existing object for updateto a context. It's doing it for a delete, but you can also to it for anupdate.ctx.AttachTo("Products", product); //you are attaching an existingobject update that you retrieved earlier in code which was in a detachedstate.product.ID = savedID; //you are forcing a change to the object in anattached state. You saved the ID at some point, and you are using thesame ID, but you have raised the event in the object to indicate that itis 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 withcolumns using T-SQL which is generated by EF to be submitted to adatabase. 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 samecontext. You have a detached object. To update and existing object, ithas to be retrieved in a context so that it is attached in context, aproperty 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 objectin context and save the changes with the object in context.http://blogs.msdn.com/b/alexj/archive/2009/03/27/tip-9-deleting-an-object-without-retrieving-it.aspxHere is another example where you attach an existing object for updateto a context. It's doing it for a delete, but you can also to it for anupdate.ctx.AttachTo("Products", product); //you are attaching an existingobject update that you retrieved earlier in code which was in a detachedstate.product.ID = savedID; //you are forcing a change to the object in anattached state. You saved the ID at some point, and you are using thesame ID, but you have raised the event in the object to indicate that itis 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
-