在ASP.Net MVC 3项目中使用Entity Framework无法保存更改

5
学习asp.net mvc 3 + EF code-first。我对两者都很陌生。我的示例很简单,但我仍然无法使它工作。可能漏掉了一些简单而明显的东西...
我有一个类:
 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; }
}

"还有一个 DbContext:"
public class EFDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
}

一个仓库:
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();
    }
}

MVC控制器:
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);
        }
    }
}

它让我看到产品列表,打开编辑视图,根据属性集验证所有内容......当我保存已验证的更改时,它会进入HTTP POST Edit方法并进行必要的SaveChanges()操作。它没有抛出任何异常,继续将我重定向到产品列表。编辑后的项目保持不变。底层数据库(通过web.config中的connectionstrings连接)也保持不变。

尝试查找是否使用了不同的上下文...似乎我的AdminController中的repository在两个不同的Edit方法中具有不同的GetHashCode()...不知道是否能说明问题。 - horgh
我使用Ninject(同时学习DI) - horgh
默认值的ProductID不应该是Int32.MinValue吗?而不是0?另外,您是如何在什么时候将存储库传递给AdminController构造函数的? - Jude Fisher
即使如此,也不是这样的情况,因为我只尝试编辑现有行。 - horgh
3个回答

8

您需要附加在EF之外创建的实体实例,并让EF知道它已被修改。

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

当调用“Edit”方法时,MVC模型绑定器会创建Product的新实例。请记住,Web是无状态的。Get和Post请求不会发生在同一个控制器实例中。 - Eranga
那么,在这种情况下使用 Attach 应该是相当普遍的,对吗?或者整体上有更好的方法吗? - horgh
2
@horgh 正确的方式是使用ViewModel与视图进行交互,并在控制器中将它们来回映射到模型实例。 - Eranga
它是 Code First 方法的替代方案吗? - horgh
你可能也想在 if (ModelState.IsValid){ ... context.saveChanges(); } 内进行编辑。 - ghiscoding
显示剩余4条评论

3

SaveChanges 之前,您应该将 Product 实例附加到上下文中。

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

1

确实,你应该附加。

假设你调用了Edit(1)。你的控制器将从数据库中加载一个ID为1的产品,并基于其属性(在视图中声明的属性)生成一个HTML视图。 一旦你离开Edit(int productId)方法并在浏览器中看到你的视图出现,你的DbContext就失去了那个ID的Product;它已经超出了范围。 如果你然后更改你的Product并提交你的表单,ASP MVC将根据你的表单字段(以及其他内容)拼凑一个新的Product对象,并将该对象传递给Edit(Product product)方法。 由于这是一个全新的Product对象,而旧的Product对象已经超出了范围,你的DbContext不知道新的Product与你的数据库有什么关系:它是一个新对象,还是一个现有对象,如果存在,它是否有任何更改? 如果你附加Product对象并将其状态设置为修改,则DbContext可以开始检查哪些属性已更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接