Как мне связать модель многие-со-многими с MVC 3 и Entity Framework Code First? - PullRequest
3 голосов
/ 31 августа 2011

Я сталкиваюсь с той же проблемой в моих приложениях MVC 3.У меня есть вид для создания нового продукта, и этот продукт может быть отнесен к одной или нескольким категориям.Вот мои классы EF Code First Model:

public class Product 
{
    public int ProductID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Category> Categories { get; set; }
}

public class Category 
{
    public int CategoryID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Product> Products { get; set; }
}

Итак, я создаю модель представления для представления создания продукта и включаю продукт и список категорий:

public class ProductEditViewModel
{
    public Product Product { get; set; }
    public List<SelectListItem> CategorySelections { get; set; }

    public ProductEditViewModel(Product product, List<Category> categories)
    {
        this.Product = product;
        CategorySelections = categories.Select(c => new SelectListItem()
        {
            Text = c.Name,
            Value = c.CategoryID.ToString(),
            Selected = (product != null ? product.Categories.Contains(c) : false)
        }).ToList();
    }
}

Итак, я отображаю представление с вводом имени и списком флажков для каждой категории (с именем «Product.Categories»).Когда моя форма отправляется обратно, я хочу сохранить продукт со связанными категориями (или, если ModelState недействителен, чтобы снова отобразить представление с выбранными категориями, которые пользователь сделал нетронутыми).

[HttpPost]
public ActionResult Create(Product product)
{
    if (ModelState.IsValid)
    {
        db.Products.Add(product);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(new ProductEditViewModel(product, db.Categories.ToList()));
}

Когда я это сделаючто и выберите одну или несколько категорий, ModelState является недействительным и возвращает представление «Правка» со следующей ошибкой проверки:

Недопустимое значение «25, 2».// 25 и 2 являются идентификаторами категорий

Для меня имеет смысл, что он не может связать 25 и 2 с фактическими объектами категории, но есть стандартный способ использования пользовательского ModelBinder, который позволил бымне перевести идентификаторы в категории и прикрепить их к контексту?

Ответы [ 3 ]

1 голос
/ 31 августа 2011

То, что вы могли бы попробовать, заключается в следующем: Привязать к вашей ViewModel вместо Product в вашем посте:

[HttpPost]
public ActionResult Create(ProductEditViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        foreach (var value in viewModel.CategorySelections
                                       .Where(c => c.Selected)
                                       .Select(c => c.Value))
        {
            // Attach "stub" entity only with key to make EF aware that the
            // category already exists in the DB to avoid creating a new category
            var category = new Category { CategoryID = int.Parse(value) };
            db.Categories.Attach(category);

            viewModel.Product.Categories.Add(category);
        }
        db.Products.Add(viewModel.Product);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(new ProductEditViewModel(
        viewModel.Product, db.Categories.ToList()));
}

Я не уверен, если это "стандартный способ ".

Редактировать

Случай return, когда модель недопустима, не может работать в моем примере выше, потому что коллекция viewModel.Product.Categories пуста, так что вы быполучить не выбранный элемент категории в представлении, а не элементы, выбранные пользователем ранее.

Я не знаю, как именно вы связываете коллекцию с представлением (вашим «списком флажков»?), но когдаиспользуя ListBox, который допускает множественный выбор, тогда, кажется, есть решение в духе этого ответа: Проблемы с выбором значений в ListBoxFor .Я только что спросил Дарина в комментариях, будет ли список идентификаторов выбранных элементов также привязан к ViewModel в действии после публикации, и он подтвердил это.

1 голос
/ 01 сентября 2011

Спасибо @Slauma, это сделало меня на правильном пути.Вот мои методы создания и редактирования сообщений, которые подробно описывают, как управлять отношениями (редактирование немного сложнее, потому что оно должно добавлять элементы, которые не существуют в базе данных, и удалять элементы, которые были удалены и существуют в базе данных).Я добавил свойство SelectedCategories (Список целых) в свой ProductEditViewModel для хранения результата из формы.

[HttpPost]
public ActionResult Create(ProductEditViewModel)
{
    viewModel.Product.Categories = new List<Category>();

    foreach (var id in viewModel.SelectedCategories)
    {
        var category = new Category { CategoryID = id };
        db.Category.Attach(category);

        viewModel.Product.Categories.Add(category);
    }

    if (ModelState.IsValid)
    {
        db.Products.Add(viewModel.Product);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(new ProductEditViewModel(viewModel.Product, GetCategories()));
}

Для метода Edit мне пришлось запросить базу данных для текущего продукта, а затем сравнить ее сviewModel.

[HttpPost]
public ActionResult Edit(ProductEditViewModel viewModel)
{
    var product = db.Products.Find(viewModel.Product.ProductID);

    if (ModelState.IsValid)
    {
        UpdateModel<Product>(product, "Product");

        var keys = product.CategoryKeys; // Returns CategoryIDs

        // Add categories not already in database
        foreach (var id in viewModel.SelectedCategorys.Except(keys))
        {
            var category = new Category { CategoryID = id }; // Create a stub
            db.Categorys.Attach(category);

            product.Categories.Add(Category);
        }

        // Delete categories not in viewModel, but in database
        foreach (var id in keys.Except(viewModel.SelectedCategories))
        {
            var category = product.Categories.Where(c => c.CategoryID == id).Single();

            product.Categories.Remove(category);
        }

        db.SaveChanges();
        return RedirectToAction("Index");
    }
    else
    {
        // Update viewModel categories so it keeps users selections
        foreach (var id in viewModel.SelectedCategories)
        {
            var category = new Category { CategoryID = id }; // Create a stub
            db.Categories.Attach(category);

            viewModel.Product.Categories.Add(category);
        }
    }

    return View(new ProductEditViewModel(viewModel.Product, GetCategories()));
}

Это больше кода, на который я надеялся, но на самом деле он довольно эффективен с использованием заглушек и только добавлением / удалением того, что изменилось.

0 голосов
/ 31 августа 2011

У меня была похожая проблема несколько дней назад. Завершено использованием "hack" - MVC 3 - Привязка к сложному типу со свойством типа List

Пожалуйста, оставьте сообщение, если вы найдете альтернативный способ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...