多对多关系基本示例(MVC3)
本文关键字:MVC3 关系 | 更新日期: 2023-09-27 17:54:38
我有一个mvc c#项目,我有一个FoodItem和FoodItemCategory的模型。两个模型如下图所示:
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属性中的特定类别。如果存在,则设置复选框的checked属性。如果您选中一个框并单击save,那么在控制器上的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);
}
我可以保存一次分类。如果我返回到视图中,并单击save,就会收到以下错误:
不能在唯一索引中插入重复的值。[表名=>FoodItemCategoryFoodItems,约束名= PK_FoodItemCategoryFoodItems_00000000000000A8]描述:在执行当前web请求期间发生了未处理的异常。请查看堆栈跟踪,了解有关错误及其在代码中的起源的更多信息。
Exception Details: System.Data.SqlServerCe.SqlCeException:一个重复的值不能插入到一个唯一索引中。[Table name = FoodItemCategoryFoodItems,Constraint name =>PK_FoodItemCategoryFoodItems_00000000000000A8]
源错误:
第97行:context.Entry(foodItem)。状态= EntityState.Modified;第98行:foodItem。Categories =类别;第99行:context.SaveChanges();第100行:返回RedirectToAction("Index");第101行:}
不确定是否重要,但我正在使用SQLServer精简版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哪些关系被删除了,因为你不传递未选中类别的信息
更多关于分离对象图和处理关系的描述在这里。这是关于ObjectContext API,但DbContext API只是包装,所以同样的限制仍然存在。
除了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);
}