【问题标题】:How do I ModelBind a many-to-many relationship with MVC 3 and Entity Framework Code First?如何使用 MVC 3 和 Entity Framework Code First 建立多对多关系模型?
【发布时间】:2011-08-31 15:54:27
【问题描述】:

我在我的 MVC 3 应用程序中遇到了同样的问题。我有一个创建新产品的视图,并且该产品可以分配到一个或多个类别。这是我的 EF Code First 模型类:

public class Product 
{
    public int ProductID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Category> Categories { get; set; }
}

public class Category 
{
    public int CategoryID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Product> Products { get; set; }
}

因此,我为创建产品视图创建了一个视图模型,并包含产品和类别列表:

public class ProductEditViewModel
{
    public Product Product { get; set; }
    public List<SelectListItem> CategorySelections { get; set; }

    public ProductEditViewModel(Product product, List<Category> categories)
    {
        this.Product = product;
        CategorySelections = categories.Select(c => new SelectListItem()
        {
            Text = c.Name,
            Value = c.CategoryID.ToString(),
            Selected = (product != null ? product.Categories.Contains(c) : false)
        }).ToList();
    }
}

因此,我渲染了一个视图,其中包含名称的输入和每个类别的复选框列表(名为“Product.Categories”)。当我的表单被发回时,我想保存产品及其关联的类别(或者如果 ModelState 无效,则重新显示具有用户所做的类别选择的视图)。

[HttpPost]
public ActionResult Create(Product product)
{
    if (ModelState.IsValid)
    {
        db.Products.Add(product);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(new ProductEditViewModel(product, db.Categories.ToList()));
}

当我这样做并选择一个或多个类别时,ModelState 无效并返回带有以下验证错误的编辑视图:

值“25,2”无效。 // 25 和 2 是 CategoryIDs

它不能将 25 和 2 绑定到实际的类别对象中,这对我来说是有意义的,但是是否有一种标准方法可以使用自定义 ModelBinder 允许我将 ID 转换为类别并将它们附加到上下文中?

【问题讨论】:

    标签: entity-framework asp.net-mvc-3 viewmodel model-binding


    【解决方案1】:

    可以尝试以下方法:在您的发布操作中绑定到您的 ViewModel 而不是 Product

    [HttpPost]
    public ActionResult Create(ProductEditViewModel viewModel)
    {
        if (ModelState.IsValid)
        {
            foreach (var value in viewModel.CategorySelections
                                           .Where(c => c.Selected)
                                           .Select(c => c.Value))
            {
                // Attach "stub" entity only with key to make EF aware that the
                // category already exists in the DB to avoid creating a new category
                var category = new Category { CategoryID = int.Parse(value) };
                db.Categories.Attach(category);
    
                viewModel.Product.Categories.Add(category);
            }
            db.Products.Add(viewModel.Product);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    
        return View(new ProductEditViewModel(
            viewModel.Product, db.Categories.ToList()));
    }
    

    我不确定这是否是“标准方式”。

    编辑

    return 模型无效时的情况在我上面的示例中不起作用,因为viewModel.Product.Categories 集合为空,因此您将在视图中没有选择的类别项目,而不是用户之前选择的项目。

    我不知道您如何将集合准确绑定到视图(您的“复选框列表”?),但是当使用允许多项选择的ListBox 时,似乎有一个与此答案类似的解决方案:Challenges with selecting values in ListBoxFor。我刚刚在 cmets 中询问了 Darin,所选项目 id 的列表是否也会在 post 操作中绑定到 ViewModel,他确认了这一点。

    【讨论】:

      【解决方案2】:

      感谢@Slauma,这让我走上了正轨。这是我的 Create 和 Edit 发布方法,详细说明了如何管理关系(编辑有点棘手,因为它必须添加数据库中不存在的项目并删除已删除但确实存在于数据库中的项目)。我在我的 ProductEditViewModel 中添加了一个 SelectedCategories 属性(整数列表)来保存表单的结果。

      [HttpPost]
      public ActionResult Create(ProductEditViewModel)
      {
          viewModel.Product.Categories = new List<Category>();
      
          foreach (var id in viewModel.SelectedCategories)
          {
              var category = new Category { CategoryID = id };
              db.Category.Attach(category);
      
              viewModel.Product.Categories.Add(category);
          }
      
          if (ModelState.IsValid)
          {
              db.Products.Add(viewModel.Product);
              db.SaveChanges();
              return RedirectToAction("Index");
          }
      
          return View(new ProductEditViewModel(viewModel.Product, GetCategories()));
      }
      

      对于 Edit 方法,我必须在数据库中查询当前产品,然后将其与 viewModel 进行比较。

      [HttpPost]
      public ActionResult Edit(ProductEditViewModel viewModel)
      {
          var product = db.Products.Find(viewModel.Product.ProductID);
      
          if (ModelState.IsValid)
          {
              UpdateModel<Product>(product, "Product");
      
              var keys = product.CategoryKeys; // Returns CategoryIDs
      
              // Add categories not already in database
              foreach (var id in viewModel.SelectedCategorys.Except(keys))
              {
                  var category = new Category { CategoryID = id }; // Create a stub
                  db.Categorys.Attach(category);
      
                  product.Categories.Add(Category);
              }
      
              // Delete categories not in viewModel, but in database
              foreach (var id in keys.Except(viewModel.SelectedCategories))
              {
                  var category = product.Categories.Where(c => c.CategoryID == id).Single();
      
                  product.Categories.Remove(category);
              }
      
              db.SaveChanges();
              return RedirectToAction("Index");
          }
          else
          {
              // Update viewModel categories so it keeps users selections
              foreach (var id in viewModel.SelectedCategories)
              {
                  var category = new Category { CategoryID = id }; // Create a stub
                  db.Categories.Attach(category);
      
                  viewModel.Product.Categories.Add(category);
              }
          }
      
          return View(new ProductEditViewModel(viewModel.Product, GetCategories()));
      }
      

      这是我希望的更多代码,但使用存根并仅添加/删除已更改的内容实际上非常有效。

      【讨论】:

      • 啊,最好先添加类别检查模型有效性!
      • 我正在尝试做同样的事情。但是,存根创建给了我一个错误。 'id' 不是 SelectListItem 吗?您似乎在发布问题后更改了 ViewModel。
      • @Omkar 没错,我的 ViewModel 现在将 SelectedCategories 定义为 List
      【解决方案3】:

      几天前我遇到了类似的问题。最终使用了“黑客” - MVC 3 - Binding to a Complex Type with a List type property

      如果您找到替代方式,请留言。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-01-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多