Базовый пример отношения «многие ко многим» (MVC3) - PullRequest
1 голос
/ 01 мая 2011

У меня есть проект MVC3 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 моей модели. Если он существует, я устанавливаю свойство флажка. Если вы установите флажок и нажмете сохранить, в действии 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] Описание: во время выполнения текущего веб-запроса произошло необработанное исключение. Пожалуйста, просмотрите трассировку стека для получения дополнительной информации об ошибке и о том, где она возникла в коде.

Сведения об исключении: System.Data.SqlServerCe.SqlCeException: дублирующее значение не может быть вставлено в> уникальный индекс. [Имя таблицы = FoodItemCategoryFoodItems, Имя ограничения => PK_ FoodItemCategoryFoodItems _00000000000000A8]

Ошибка источника:

Строка 97: context.Entry (foodItem) .State = EntityState.Modified; Строка 98: foodItem.Categories = Categories; Строка 99: context.SaveChanges (); Строка 100: return RedirectToAction («Индекс»); Строка 101:}

Не уверен, имеет ли это значение, но я использую SQLServer Compact Edition 4. Правильно ли я поступаю? Какова нормальная практика кодирования для чего-то подобного? Я знаю, что такая же ситуация возникает ежедневно, так как эта же модель отношений используется во многих ситуациях, таких как блоги и т. Д.

Ответы [ 2 ]

4 голосов
/ 01 мая 2011

Попробуйте что-то вроде этого (не проверено):

[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, какие отношения удалены, потому что вы не передаете информацию о непроверенных категориях

Подробнее о графах обособленных объектов и отношениях обработки описано здесь .Речь идет об API-интерфейсе ObjectContext, но API-интерфейс DbContext является просто оберткой, поэтому все еще существуют те же ограничения.

0 голосов
/ 28 июня 2011

В дополнение к ответу Ладислава вы можете избавиться от части Request []. Split (), используя 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);
}
...