1

FoodItem と FoodItemCategory のモデルを持つ MVC3 C# プロジェクトがあります。2 つのモデルは次のように表示されます。

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

最初に Scaffolder から生成された _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 プロパティを設定します。チェックボックスをオンにして [保存] をクリックすると、コントローラーの 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);
    }

カテゴリを一度保存​​できます。ビューに戻って [保存] をクリックすると、次のエラーが表示されます。

一意のインデックスに重複する値を挿入することはできません。[ テーブル名 = >FoodItemCategoryFoodItems,制約名 = PK_ FoodItemCategoryFoodItems _00000000000000A8 ] 説明: 現在の Web リクエストの実行中に未処理の例外が発生しました。>エラーの詳細とコード内のどこでエラーが発生したかについては、スタック トレースを確認してください。

例外の詳細: System.Data.SqlServerCe.SqlCeException: 一意のインデックスに重複する値を挿入することはできません。[ テーブル名 = FoodItemCategoryFoodItems、制約名 = >PK_ FoodItemCategoryFoodItems _00000000000000A8 ]

ソース エラー:

行 97: context.Entry(foodItem).State = EntityState.Modified; 行 98: foodItem.Categories = カテゴリ; 行 99: context.SaveChanges(); 100 行目: return RedirectToAction("Index"); 101 行目: }

問題があるかどうかはわかりませんが、SQLServer Compact Edition 4 を使用しています。これは正しい方法ですか? このようなものの通常のコーディング方法は何ですか? この同じ関係モデルがブログなどの多くの状況で使用されているため、これと同じ状況が毎日発生していることはわかっています。

4

2 に答える 2

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と言うことはできません。

デタッチされたオブジェクトグラフと処理関係の詳細については、ここで説明します。これはObjectContextAPIに関するものですが、DbContext APIはそのラッパーにすぎないため、同じ制限がまだ存在します。

于 2011-05-01T19:47:02.773 に答える