Question

Studying asp.net mvc 3 + EF code-first. I am new to both. My example is trivial, but I still can't make it work. Missing something simple and obvious...

I've got a class:

 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 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; }
}

and a DbContext:

public class EFDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
}

and a repository:

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();
    }
}

The mvc controller:

public class AdminController : Controller
{
    private IProductRepository repository;

    public AdminController(IProductRepository repo)
    {
        repository = repo;
    }

    public ViewResult Index()
    {
        return View(repository.Products);
    }

    public ViewResult Edit(int productId)
    {
        Product 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
        {
            // there is something wrong with the data values
            return View(product);
        }
    }
}

It lets me see the list of products, opens the edit view, validates everything according to the set of attributes...

When I save validated changes it goes to the Http Post Edit method and makes the necessary SaveChanges().

It doesn't throw any exceptions, it goes on and redirect me to the list of products.

The edited item stays unchanged.

The underlying database (connected through connectionstrings in web.config) stays unchanged as well.

Was it helpful?

Solution

You need to attach the entity instance created outside EF and let EF know that it has been modified.

public void SaveProduct(Product product)
{
    if (product.ProductID == 0)
    {
        context.Products.Add(product);
    }
    else
    {
        context.Products.Attach(product);
        context.Entry(product).State = EntityState.Modified;
    }

    context.SaveChanges();
}

OTHER TIPS

You should Attach the Product instances to the context before SaveChanges

public void SaveProduct(Product product)
{
    if (product.ProductID == 0)
        context.Products.Add(product);
    else
    {    
        context.Products.Attach(product);
        context.Entry(product).State = EntityState.Modified;
    }
    context.SaveChanges();
}

Indeed, you should attach.

Say you call Edit(1). Your controller will load a Product with ID = 1 from your DB and generate an HTML view based upon its properties (the ones you've declared in your view anyway). As soon as you leave the Edit(int productId) method and you see your view appear in your browser, your DbContext has lost the Product with that ID; it has gone out of scope. If you then make changes to your Product and submit your form, ASP MVC will cobble together a new Product object based upon your form fields (among other things) and pass that object to the Edit(Product product) method. Since this is a completely new Product object and the old Product object went out of scope anyway, you DbContext has no idea how the new Product relates to your DB: is it a new object, is it an existing object, if it exists does it have any changes? If you attach the Product object and set its state to modified, then the DbContext can start checking which properties have changed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top