我如何首先用MVC 3和实体框架代码模型绑定多对多关系?



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

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无效,它返回带有以下验证错误的Edit视图:

值"25,2"无效。//25和2为categoryid

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

可以尝试如下:绑定到你的ViewModel而不是Product在你的post动作:

[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时,似乎有一个解决方案沿着这个答案的路线:在ListBoxFor中选择值的挑战。我刚才在评论中问Darin,所选项目id列表是否也会在post操作中绑定到ViewModel,他确认了。

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

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

这是更多的代码,我希望它会,但它实际上是相当有效的使用存根,只添加/删除已更改的

几天前我也遇到过类似的问题。最后使用了一个"hack"——MVC 3——绑定到一个带有List类型属性的复杂类型

如果您找到其他方法,请留言

相关内容

  • 没有找到相关文章

最新更新