Привязка модели ASP.NET MVC 2 RC к NHibernate и раскрывающимся спискам

У меня проблема с привязкой модели в моем приложении ASP.NET MVC 2 RC, которое использует NHibernate для доступа к данным. Мы пытаемся создать приложение в стиле Ruby on Rails и иметь очень простую архитектуру, в которой объекты предметной области используются на всем пути от базы данных до представления.

Приложение имеет пару объектов предметной области, которые можно проиллюстрировать следующими двумя классами:

public class Product {
    ...

    public Category Category { get; set; }      
}

public class Category {
    public int Id { get; set; }
    public string Name { get; set; }
}

В представлении, отображающем форму редактирования, есть следующий оператор для отображения раскрывающегося списка:

<%= Html.DropDownListFor(model => model.Category.Id, 
       new SelectList(ViewData["categories"] as IList<Category>, "Id", "Name"), 
       "-- Select Category --" ) %>

Не обращайте внимания на использование «нетипизированных» данных представления для хранения коллекции категорий.

Метод действия, который получает публикацию формы, аналогичен следующему. Обратите внимание, что атрибут TransactionFilter добавляет обработку транзакций NHibernate и фиксирует транзакцию, если исключений не возникает и проверка прошла успешно.

[HttpPost]
[TransactionFilter]
public ActionResult Edit(int id, FormCollection collection) {
    var product = _repository.Load(id);

    // Update the product except the Id
    UpdateModel(product, null, null, new[] {"Id"}, collection);

    if (ModelState.IsValid) {
      return RedirectToAction("Details", new {id});
    }
    return View(product);
}

Моя проблема в том, что для product.Category.Id установлено значение, выбранное в форме, например. Категория.Id = "2". Использование связывателя модели по умолчанию приводит к следующему типу исключения NHibernate:

identifier of an instance of Name.Space.Entities.Category was altered from 4 to 2

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

Я предполагаю, что для решения этой проблемы можно создать собственный ModelBinder, но есть ли более простой способ сделать эту работу? Могут ли (и должны ли) быть изменены объекты домена, чтобы справиться с этим?


person HakonB    schedule 12.01.2010    source источник
comment
Вы нашли решение этой проблемы? У меня точно такая же проблема.   -  person nabeelfarid    schedule 08.06.2011
comment
Нет, я так и не нашел хорошего решения (впрочем, мы не тратили много времени на его поиски). Мы решили проблему явно за пределами связующего.   -  person HakonB    schedule 08.06.2011
comment
Не могли бы вы подсказать, как вы на самом деле справляетесь с этим вне переплета. Вы вообще не используете TryUpdateModel/UpdateModel?   -  person nabeelfarid    schedule 08.06.2011
comment
Это было давно, но я думаю, что сделал быстрый обходной путь, используя TryUpdateModel, но исключив ассоциацию, а затем вручную обновив послесловие этого свойства. Я добавил простой ответ, который иллюстрирует, как мы это сделали.   -  person HakonB    schedule 08.06.2011


Ответы (3)


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

@Html.DropDownListFor(x => x.Employee.Id, new SelectList(ViewBag.Employees, "Id", "DisplayName"))

by

@Html.DropDownListFor(x => x.Employee, new SelectList(ViewBag.Employees, "Id", "DisplayName"))

Поэтому я удалил «.Id», как предложил Брайан. До изменения модель содержала только идентификатор сотрудника, а после изменения Binder также загрузил в модель все сведения о сотруднике.

person Björn Boxstart    schedule 03.10.2012

Я использовал аналогичные методы с классами Linq to SQL раньше без проблем. Я не думаю, что для этого вам понадобится собственный ModelBinder. UpdateModel должен обновлять класс Product, который вы ему передаете, а не прикрепленный к нему подкласс Category. Проверьте HTML-код, сгенерированный помощником DropDownListFor. Как называется элемент? Это должно быть имя поля внешнего ключа в таблице «Товары» (например, «CategoryID» или «Product.CategoryID», а не «Category.Id»). Если это «Category.Id», попробуйте изменить первый параметр DropDownListFor либо на «model => model.Category», либо на «model => model.CategoryID» (или на любое другое поле внешнего ключа). Это должно привести к тому, что UpdateModel будет обновлять только поле внешнего ключа в классе Product, а не идентификатор класса категории.

person Bryan    schedule 18.01.2010
comment
Внешний ключ недоступен, так как отношение полностью обрабатывается NHibernate. В итоге я просто обрабатывал случай категории вручную и не создавал привязку пользовательской модели. - person HakonB; 11.03.2010

Решение, которое мы выбрали в то время, было примерно таким:

TryUpdateModel(product, null, null, new[] {"Category"}, collection);
int categoryId;
if (int.TryParse(collection["Category.Id"], NumberStyles.Integer, CultureInfo.InvariantCulture, out categoryId) && categoryId > 0) {
    product.Category = _categoryRepository.Load(categoryId);
}
else {
    product.Category = null;
}

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

person HakonB    schedule 08.06.2011