Entity Framework в MVC 3: Как вставить модель с внешним ключом? - PullRequest
0 голосов
/ 14 августа 2011

У меня есть Модель продукта, которая содержит внешний ключ к записи ProductCategory и выглядит следующим образом.

public class ProductModel : IProductModel
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public string ProductDescription { get; set; }
    public string ProductImagePath { get; set; }
    public decimal? PricePerMonth { get; set; }
    public bool ProductActive { get; set; }
    public ProductCategory ProductCategory { get; set; }
}

По какой-то причине каждый раз, когда создается новый продукт, также создается новая категория продукта, для всех его значений в базе данных устанавливается значение NULL, за исключением поля ProductCategoryID, которое создается автоматически.

В моем представлении Создать у меня есть форма, которая содержит раскрывающийся список, заполненный ProductCategories. Как только форма отправлена, вызывается действие Создать (продукт продукта) и создается новый продукт. На этом этапе также создается новая ProductCategory, поведение которой мне не нужно.

My Create (продукт продукта) Действие выглядит следующим образом:

    //
    // POST: /Product/Create
    [HttpPost]
    public ActionResult Create(Product product)
    {
        int productCategoryID = product.ProductCategory.ProductCategoryID;
        product.ProductCategoryID = productCategoryID;
        _productService.InsertProduct(product);
    }

... и мои методы InsertProduct выглядят так ...

    public void InsertProduct(Product product)
    {
        Db.Products.AddObject(product);
        Save();
    }

Почему этот код вызывает создание новой категории продукта каждый раз?

РЕДАКТИРОВАТЬ: Добавление определения таблицы, в ответ на данный ответ, который запрашивает дополнительную информацию:

TABLE Product (
ProductID INT PRIMARY KEY IDENTITY(1,1),
ProductCategoryID INT, -- FK to ProductCategory 
ProductName NVARCHAR(255),
ProductDescription NVARCHAR(MAX),
ProductImagePath NVARCHAR(1024),
PricePerMonth DECIMAL(7,2), -- ex 11111.11
ProductActive BIT NOT NULL DEFAULT(1)
)

TABLE ProductCategory (
ProductCategoryID INT PRIMARY KEY IDENTITY(1,1),
ProductCategoryName NVARCHAR(255),
ProductCategoryDescription NVARCHAR(MAX),
ProductCategoryActive BIT NOT NULL DEFAULT(1)
)

РЕДАКТИРОВАТЬ 2:

Я до сих пор не понимаю, почему именно, но мне удалось обойти эту проблему с помощью следующих изменений, внесенных в мое действие Создать (продукт):

var productCategoryID = product.ProductCategory.ProductCategoryID;
product.ProductCategoryID = productCategoryID;
var newProduct = new Product
        {
            ProductName = product.ProductName,
            ProductDescription = product.ProductDescription,
            PricePerMonth = product.PricePerMonth,
            ProductImagePath = product.ProductImagePath,
            ProductCategoryID = productCategoryID
        };

_productService.InsertProduct(newProduct);

РЕДАКТИРОВАТЬ 3:

Я все еще пытаюсь обернуть голову вокруг всего этого, но ... Я обнаружил, что эта проблема имеет отношение к члену ProductCategory класса Product. Из того, что я могу сказать, ModelBinding автоматически заполняет это свойство новой ProductCategory при каждой отправке формы. Я могу последовательно избежать проблемы, с которой я столкнулся здесь, явно установив Product.ProductCategory = null;

Ответы [ 5 ]

4 голосов
/ 16 августа 2011

Мне было любопытно узнать о вашей проблеме, поэтому я получил несколько советов от известного автора и инструктора по MVC и выяснил следующее. Основные понятия доступны в блоге MSDN .

Процесс привязки модели MVC связывает данные формы из представления с моделью, которую вы передаете действию Создать. Он находит соответствующие значения в данных формы для привязки к объектам Product и ProductCategory и выполняет привязку модели.

Проблема в вашем случае заключается в том, что Entity Framework не знает об объектах, потому что вы создали объекты Product и ProductCategory самостоятельно, вне контекста EF, а не сущности из контекста EF. В вашем случае вы используете EF 4.1 с DbContext, но это также относится к различным методам EF 4.0 с ObjectContext.

Ваш новый объект Product не существует в вашем контексте EF или базе данных - вы еще не добавили его - но ваша ProductCategory делает. Вам необходимо сопоставить объект ProductCategory, висящий на вашем новом объекте Product, с существующей ProductCategory из ваших сущностей EF. Для этого вы можете либо найти существующую ProductCategory, выполнив поиск по первичному ключу (ProductCategoryId), затем присвоив ее своему product.ProductCategory, либо вы можете «прикрепить» свой свободно плавающий объект ProductCategory к вашим сущностям. Вы передаете ему свой объект ProductCategory, и он, по сути, делает то же самое, что и поиск существующего объекта - он ищет объект по первичному ключу. Оба метода по существу берут существующий объект ProductCategory, сообщают EF, что ваш объект является сущностью, и устанавливают для «состояния» сущности значение «Не изменено», поэтому при вызове context.SaveChanges() он игнорирует сущность ProductCategory и поэтому не находит изменений для отправки. вернуться к базовой базе данных.

Итак, перед тем, как добавить свой продукт в контекст EF, сначала необходимо A ) найти существующую сущность ProductCategory, которая уже присоединена к EF, B ) присоединить отсоединенный Для объекта ProductCategory или C ) задайте product.ProductCategoryId и обнулите ссылку на product.ProductCategory, чтобы EF нашла ProductCategory для вас. Вы выбрали вариант С.

Вариант A (Найти существующую присоединенную сущность):

public ActionResult Create(Product product)
{
    // find an attached entity based on primary key
    var productCat = 
        Db.ProductCategories.Find(product.ProductCategory.ProductCategoryId); 
    product.ProductCategoryId = productCat.ProductCategoryId;
    product.ProductCategory = productCat;
    _productService.InsertProduct(product);
}

Опция B (Присоединить отдельное лицо):

public ActionResult Create(Product product)
{
    // attach an entity - EF will find the entity to attach using the entity's primary key
    Db.ProductCategories.Attach(product.ProductCategory); 
    product.ProductCategoryId = product.ProductCategory.ProductCategoryId;
    _productService.InsertProduct(product);
}

Опция C (Обнулить ссылку на сущность):

public ActionResult Create(Product product)
{
    product.ProductCategoryId = product.ProductCategory.ProductCategoryId;
    product.ProductCategory = null;
    _productService.InsertProduct(product);
}
1 голос
/ 14 августа 2011

@ campbelt: Не уверен, почему это происходит.Вот как я это сделал.Я не передавал свой доменный объект в представление, но прошел через модель представления под названием EditGrantApplicationViewModel.В моем сценарии у меня есть форма заявки на грант.В этой форме пользователь может выбрать банк из выпадающего списка.Ниже приведена моя модель частичного представления, содержащая сведения о раскрывающемся списке банка.

public class EditGrantApplicationViewModel
{
   // Other properties

   public int BankId { get; set; }
   public List<Bank> Banks { get; set; }
}

Мое представление для этого раскрывающегося списка выглядит следующим образом:

<td valign="top"><label>Bank:</label> <span class="red">*</span></td>
<td>
   @Html.DropDownListFor(x => x.BankId, new SelectList(Model.Banks, "Id", "Name", Model.BankId), "-- Select --")<br>
   @Html.ValidationMessageFor(x => x.BankId)
</td>

Мой метод действия Создать:

public ActionResult Create(EditGrantApplicationViewModel viewModel)
{
   // Check input parameter

   if (!ModelState.IsValid)
   {
      viewModel.Banks = bankService.GetAll();

      return View("Create", viewModel);
   }

   // Map from view model to domain object
   GrantApplication grantApplication = (GrantApplication)grantApplicationMapper.Map(viewModel, typeof(EditGrantApplicationViewModel), typeof(GrantApplication));

   // Insert to database
   grantApplicationService.Insert(grantApplication);

   return RedirectToRoute(Url.GrantApplicationIndex());
}

Частичный объект GrantApplication:

public class GrantApplication
{
   public int Id { get; set; }

   public int BankId { get; set; }
}

Сопоставление заполнит свойство BankId в моем объекте GrantApplication (которое было выбрано) из раскрывающегося списка, и это будет добавлено в столбец в моем приложении GrantApplicationтаблица.

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

Каково значение productCategoryID в вашем действии Create после его назначения?Это значение выбрано из выпадающего списка в форме, или это новое значение?

Если вы определили отношение внешнего ключа от первичного ключа в таблице ProductCategory к внешнему ключу в таблице Product, то при каждом сохранении Product со значением ProductCategoryID это значение ProductCategoryID должно существоватьв таблице ProductCategory.

Поэтому, если вы сохраняете продукты со значениями ProductCategoryID, которых нет в таблице ProductCategory, запись должна быть вставлена ​​в таблицу ProductCategory, чтобы вставить запись в таблицу Product.

Кроме того, обычно ваше действие Create должно принимать объект ProductModel из представления Create.Это модель данных вашей страницы, из которой вы извлекаете значения, необходимые для создания сущности Product из контекста EF (объект "entity"), а затем сохраняете сущность Product.

Я не совсем уверен насчет ваших Product против ProductModel классов.

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

Судя по вашему описанию, проблема, скорее всего, связана с кодом, в котором вы создаете выпадающий список.

В вашем контроллере создайте IEnumerable<SelectListItem> таблицы ProductCategory и передайте ее в ViewBag, например, ViewBag.ProductCategories. Затем, по вашему мнению, используйте для раскрывающегося списка следующее:

@Html.DropDownListFor(model => model.ProductCategoryId, (IEnumerable<SelectListItem>)ViewBag.ProductCategories)

Затем в вашем действии [HttpPost] Создать контроллер ProductCategoryId уже будет правильно заполнен.

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

Категория продукта была создана, потому что вы устанавливаете ProductCategoryID для вашего нового объекта PRODUCT.

и мне интересно, почему вы устанавливаете категорию товаров таким образом? и откуда взялся этот идентификатор категории?

в любом случае, если вы используете платформу сущностей, вы должны убедиться, что у вас есть ПРАВЫЕ отношения в SQL-сервере (например, один-к-одному или один-ко-многим). Некоторые из неожиданных действий в структуре сущностей связаны с неправильным дизайном базы данных на сервере SQL.

в любом случае, если это не поможет вам дать мне точное отображение таблицы и отношения и напишите более точно, чего вы пытаетесь достичь.

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