简体   繁体   English

多对多关系基本示例 (MVC3)

[英]Many-To-Many Relationship Basic Example (MVC3)

I have a MVC3 C# project that I have a model of FoodItem and FoodItemCategory.我有一个 MVC3 C# 项目,我有一个 FoodItem 和 FoodItemCategory 的 model。 The two models are shown as follows:两种模型如下图所示:

public class FoodItem
{
    public int ID { get; set; }
    [Required]
    public string Name { get; set; }
    public string Description { get; set; }
    public virtual ICollection<FoodItemCategory> Categories { get; set; }
    public DateTime CreateDate { get; set; }
}

public class FoodItemCategory {
    public int ID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public virtual ICollection<FoodItem> FoodItems { get; set; }
    public DateTime CreateDate { get; set; }
} 

I have a _CreateOrEdit.cshtml view that was initially generated from the Scaffolder and I modified it to include all of the Categories and check the box that the food item belongs to.我有一个最初从 Scaffolder 生成的 _CreateOrEdit.cshtml 视图,我对其进行了修改以包含所有类别并选中食品所属的框。 A food item could have many or all of the categories.一个食品项目可以有许多或所有类别。 The view looks like the following:视图如下所示:

@model StackOverFlowIssue.Models.FoodItem
<div class="editor-label">
    @Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.Name)
    @Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
    @Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.Description)
    @Html.ValidationMessageFor(model => model.Description)
</div>
<div class="editor-label">
    @Html.LabelFor(model => model.Categories)
</div>
<div class="editor-field">
    @foreach (var FoodItemCategory in (IEnumerable<StackOverFlowIssue.Models.FoodItemCategory>)ViewBag.Categories){
        <input type="checkbox" name="FoodItemCategoryId" value="@FoodItemCategory.ID" 
        @foreach(var c in Model.Categories){
            if(c.ID == FoodItemCategory.ID){ 
                @String.Format("checked=\"checked\"")
            } 
        } 
        />
        @FoodItemCategory.Name 
        <br />
    } 
</div>
@Html.Hidden("CreateDate", @DateTime.Now)

As you can see, I have a nested loop that creates a checkbox for each category, and while it is creating each category, I loop through and check for that particular category in the Categories property of my model.如您所见,我有一个为每个类别创建一个复选框的嵌套循环,当它创建每个类别时,我循环并在我的 model 的 Categories 属性中检查该特定类别。 If it exists, I set the checked property of the checkbox.如果存在,我设置复选框的选中属性。 If you check a box and click save, on the HttpPost action on the controller, I am performing the following:如果您选中一个框并单击保存,则在 controller 上的 HttpPost 操作上,我将执行以下操作:

    [HttpPost]
    public ActionResult Edit(FoodItem foodItem)
    {
        if (ModelState.IsValid)
        {
            var cList = Request["CategoryId"].Split(',');
            List<FoodItemCategory> categories = new List<FoodItemCategory>();

            foreach (var c in cList) {
                var ci = Convert.ToInt32(c);
                FoodItemCategory category = context.FoodItemCategories.Single(x => x.ID == ci);
                categories.Add(category);
            }

            context.Entry(foodItem).State = EntityState.Modified;
            restaurant.Categories = categories;
            context.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(foodItem);
    }

I am able to save the categories one time.我可以一次保存类别。 If I go back into the view, and just click save, I receive the following error:如果我 go 回到视图中,然后单击保存,我会收到以下错误:

A duplicate value cannot be inserted into a unique index.不能将重复值插入唯一索引。 [ Table name = >FoodItemCategoryFoodItems,Constraint name = PK_ FoodItemCategoryFoodItems _00000000000000A8 ] Description: An unhandled exception occurred during the execution of the current web request. [表名=>FoodItemCategoryFoodItems,约束名= PK_FoodItemCategoryFoodItems_00000000000000A8 ] 说明:当前web请求执行过程中出现未处理异常。 Please >review the stack trace for more information about the error and where it originated in the code.请>查看堆栈跟踪以获取有关错误及其源自代码的位置的更多信息。

Exception Details: System.Data.SqlServerCe.SqlCeException: A duplicate value cannot be inserted into >a unique index.异常详细信息:System.Data.SqlServerCe.SqlCeException:无法将重复值插入 > 唯一索引。 [ Table name = FoodItemCategoryFoodItems,Constraint name = >PK_ FoodItemCategoryFoodItems _00000000000000A8 ] [ 表名 = FoodItemCategoryFoodItems,约束名称 = >PK_ FoodItemCategoryFoodItems _00000000000000A8 ]

Source Error:源错误:

Line 97: context.Entry(foodItem).State = EntityState.Modified;第 97 行: context.Entry(foodItem).State = EntityState.Modified; Line 98: foodItem.Categories = categories;第 98 行:foodItem.Categories = 类别; Line 99: context.SaveChanges();第 99 行:context.SaveChanges(); Line 100: return RedirectToAction("Index");第 100 行:返回 RedirectToAction("Index"); Line 101: }第 101 行:}

Not sure if it matters but I am using SQLServer Compact Edition 4. Am I going about this the right way?不确定这是否重要,但我使用的是 SQLServer Compact Edition 4。我这样做的方式是否正确? What is the normal coding practice for something like this?像这样的事情的正常编码实践是什么? I know this same situation occurs daily since this same relationship model is used in many situations like blogs, etc.我知道同样的情况每天都会发生,因为同样的关系 model 用于许多情况,如博客等。

Try something like this (untested):尝试这样的事情(未经测试):

[HttpPost]
public ActionResult Edit(FoodItem foodItem)
{
    if (ModelState.IsValid)
    {
        int id = foodItem.Id;
        // Load food item with related categories first
        var item = context.FoodItems
                          .Include(f => f.Categories)
                          .Single(f => f.Id == id);

        // Process changed scalar values
        context.Entry(item).CurrentValues.SetValues(foodItem);

        // Brute force processing of relations
        // This can be optimized - instead of deleting all and adding all again
        // you can manually compare which relations already exists, add new and
        // remove non existing but let's make that as a homework
        item.Categories.Clear();

        var cList = Request["CategoryId"].Split(',');

        foreach (var c in cList) 
        {
            var ci = Convert.ToInt32(c);
            // Use find - if category was already loaded in the first query, it will
            // be reused without additional query to DB
            var category = context.Categories.Find(ci);
            // Now add category to attached food item to create new relation
            item.Categories.Add(category);
        }

        context.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(foodItem);
}

This can look pretty inefficient but because you are dealing with many-to-many relation where relations can be added or removed in the view it is the only way to do that.这看起来效率很低,但是因为您正在处理可以在视图中添加或删除关系的多对多关系,所以这是唯一的方法。 The reasons are:原因是:

  • You must say EF which relations are added and which are deleted你必须说 EF 添加了哪些关系,删除了哪些关系
  • If you simply add all related categories you are inserting relations again如果您只是添加所有相关类别,您将再次插入关系
  • You can't say EF which relations are deleted because you don't transfer information about unchecked categories您不能说 EF 删除了哪些关系,因为您不传输有关未检查类别的信息

More about detached object graphs and processing relations is described here .有关分离的 object 图和处理关系的更多信息,请参见此处 It is about ObjectContext API but DbContext API is just wrapper around that so the same limitations still exist.它是关于 ObjectContext API 但 DbContext API 只是围绕它的包装,因此仍然存在相同的限制。

In addition to Ladislav's answer, you can get rid of the Request[].split() part by using http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx which makes:除了 Ladislav 的回答之外,您还可以使用http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx摆脱 Request[].split() 部分使:

[HttpPost]
public ActionResult Edit(FoodItem foodItem, ICollection<int> CategoryId)
{
    if (ModelState.IsValid)
    {
        int id = foodItem.Id;
        // Load food item with related categories first
        var item = context.FoodItems
                          .Include(f => f.Categories)
                          .Single(f => f.Id == id);

        // Process changed scalar values
        context.Entry(item).CurrentValues.SetValues(foodItem);

        // Brute force processing of relations
        // This can be optimized - instead of deleting all and adding all again
        // you can manually compare which relations already exists, add new and
        // remove non existing but let's make that as a homework
        item.Categories.Clear();

        foreach (var id in CategoryID) 
        {
            // Use find - if category was already loaded in the first query, it will
            // be reused without additional query to DB
            var category = context.Categories.Find(id);
            // Now add category to attached food item to create new relation
            item.Categories.Add(category);
        }

        context.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(foodItem);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM