1

我有一個MVC3 C#項目,我有一個FoodItem和FoodItemCategory的模型。這兩種型號如下所示:多對多關係基本示例(MVC3)

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

我有一個最初從棚架產生_CreateOrEdit.cshtml看法,我修改了它包括所有類別的,檢查食品屬於框。食物可以有許多或全部類別。該視圖如下所示:

@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) 

正如你所看到的,我有一個嵌套循環創建每個類別的複選框,雖然它正在創建每個類別中,我遍歷併爲您在特定的類別我的模型的Categories屬性。如果存在,我設置複選框的選中屬性。如果你選中一個複選框,然後點擊保存,在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); 
    } 

我能夠保存類別之一的時間。如果我回去到視圖,並且只需點擊保存,我收到以下錯誤:

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. 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. [ Table name = FoodItemCategoryFoodItems,Constraint name = >PK_FoodItemCategoryFoodItems_00000000000000A8 ]

Source Error:

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

不知道它的問題,但是我用的SQLServer的精簡版4.我要對這個正確的方式?什麼是這樣的正常編碼習慣?我知道,因爲這同樣關係模型中,如博客很多情況下使用這種同樣的情況發生,每天等

+0

數據庫表用獨特的PK和抱怨創造了你正在添加一個副本。我認爲這將有助於查看錶創建SQL和context.SaveChanges()代碼。 – 2011-05-01 19:28:01

回答

4

嘗試是這樣的(未經測試):

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

這看起來非常低效,而是因爲你是處理多對多的關係,可以在視圖中添加或刪除關係,這是實現這一目標的唯一方法。其理由是:

  • 你必須說EF這關係添加和刪除
  • 如果你簡單地添加所有相關類別你再次插入關係
  • 你不能說哪個EF關係被刪除因爲您不傳輸有關未選中類別的信息

有關分離的對象圖和處理關係的更多信息,請參閱described here。它是關於ObjectContext API的,但DbContext API僅僅是封裝,所以仍然存在相同的限制。

+0

似乎Occam的剃刀適用於此。我確實承認它試圖插入一個重複的記錄,並且我需要對此進行編碼,但不知道是否有一個標準的方法可以自動執行此操作,但我想不是。腳手架只能走得這麼遠,在這種情況下,最簡單的答案(刪除現有項目並將其添加回)是正確的答案。非常感謝拉迪斯拉夫。 – Keith 2011-05-01 20:53:11

+0

它似乎不適用於我:行'context.Entry(item).CurrentValues.SetValues(foodItem);'拋出一個「集合修改」異常,因爲item和foodItem的集合不相等。 – chiccodoro 2011-06-29 07:05:38

+0

@chiccodoro:什麼樣的收藏不相等?此方法不適用於任何集合,它只複製標量值。 – 2011-06-29 10:18:03

0

除了拉吉斯拉夫的回答,您可以通過使用http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx這使得擺脫請求[]的分裂()部分:

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