Entity Framework с сильно типизированным MVC
Я использую ASP.NET MVC и ADO.NET Entity Framework вместе.
Я хочу, чтобы мои представления и контроллеры были строго типизированы.
Но как я должен обрабатывать ассоциации сущностей?
Вот простой пример:
У человека есть один отдел. Департамент имеет ноль или более людей.
![Entity Data Model of Person and Department entities]()
Мой контроллер передает экземпляр объекта Person и коллекцию всех объектов отдела в представление.
public class PersonController : Controller
{
...
//
// GET: /Person/Create
public ActionResult Create()
{
Person Model = new Person();
Model.Id = Guid.NewGuid();
ViewData["Departments"] = db.Department;
return View(Model);
}
...
}
В My View есть "Департамент" DropDownList со всеми отделами в качестве параметров.
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="Id">Id:</label>
<%= Html.TextBox("Id") %>
<%= Html.ValidationMessage("Id", "*") %>
</p>
<p>
<label for="Name">Name:</label>
<%= Html.TextBox("Name") %>
<%= Html.ValidationMessage("Name", "*") %>
</p>
<p>
<label for="Department">Family:</label>
<%= Html.DropDownList("Department", new SelectList((IEnumerable)ViewData["Departments"], "Id", "Name"))%>
<%= Html.ValidationMessage("Department", "*")%>
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
Но объект Person, отправленный в контроллер, имеет no Department и не работает!
public class PersonController : Controller
{
...
//
// POST: /Person/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Person Model)
{
try
{
db.AddToPerson(Model);
db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
...
}
Почему выбранный отдел из DropDownList "Департамент" автоматически добавлен в модель Person?
Как использовать ADO.NET Entity Framework и ASP.NET MVC с сильно типизированными представлениями и контроллерами?
Ответы
Ответ 1
Я попытаюсь направить вас с стороны MVC, так как это проблема, я полагаю,
В методе Create() вы создаете объект Person и список департаментов из базы данных, тогда оба объекта передаются в представление. Вид берет данные из списка Департамента и использует его для отображения HTML-формы - используя только Идентификатор и Имя.
На следующем шаге форма отправляется на сервер в виде набора пар ключ-значение (стандартный POST). Механизм маршрутизации принимает запрошенный url из атрибута действия и решает его в PersonController.Create(Person Model). Аргументом этого метода является Person, поэтому вложение данных происходит, создает новый экземпляр класса Person и пытается сопоставить входящие данные со свойствами Person. В случае Департамента входное значение - это идентификатор отдела (потому что это то, что вы задали как элемент значения для DropDownList), в то время как отдел свойств класса Person, вероятно, относится к типу отдела. Это несоответствие, поэтому оно не может заполнить свойство, и оно остается пустым.
Как вы можете видеть, это не ограничение DropDownList, проблема в том, что вы не можете передавать все данные Департамента в DropDownList и обновлять его во время сохранения (например, с Person) из-за характера запроса POST, и поэтому DropDownList принимает только два значения из каждого Департамента (значение и имя).
Мое обычное решение: как правило, мои модели не являются теми же классами, что и мои бизнес-объекты, я делаю это, имея два свойства в модели: получить только свойство IEnumerable и другое свойство DepartmentId (get/set). Затем я использую его следующим образом:
<%= Html.DropDownList("DepartmentId", Model.Departments) %>
Затем в действии сохранения я беру Департамент из db, используя DepartmentId, назначаю его Person и сохраняю.
В вашем случае (модели, являющиеся бизнес-объектами), я бы, вероятно, не попытался автоматически привязать Департамент к модели Person, но просто возьму Id и сделаю это сам.
Это просто предположение (я не специалист по EF), но я думаю, что у вас может быть другая проблема: если db является полем на контроллере и он воссоздается по каждому запросу, это может быть ненужным накладные расходы. Я надеюсь, что он не откроет соединение db каждый раз, пожалуйста, проверьте его.
Надеюсь, что поможет
Ответ 2
Я использую ViewModel (проверьте учебник NerdDinner, ссылки внизу).
Во-первых, вам нужно подделать ограничение внешнего ключа, расширив вашу модель в частичном:
public partial class Person
{
public int DepartmentId
{
get
{
if(this.DepartmentsReference.EntityKey == null) return 0;
else
return (int)this.DepartmentsReference.EntityKey.EntityKeyValues[0].Value;
}
set
{
this.DepartmentsReference.EntityKey =
new EntityKey("YourEntities.DepartmentSet", "Id", value);
}
}
}
Во-вторых, создайте ViewModel:
public class PersonFormViewModel
{
public SelectList Departments { get; set: }
public Person Pers { get; set; }
public PersonFormViewModel(Person p, List<Department> departments)
{
this.Pers = p;
this.Departments = new SelectList(departments, "Id", "Name", p.DepartmentId);
}
}
В-третьих, действие контроллера (сокращенный пример создания):
public ActionResult Create()
{
YourEntities entities = new YourEntities();
List<Department> departments = entities.DepartmentSet.ToList();
PersonFormViewModel viewModel =
new PersonFormViewModel(new Person(), departments);
return View(modelView);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude="Id")]Person personToCreate
{
YourEntities entities = new YourEntities(); // Probably not instantiated here
entities.AddToPersonSet(personToCreate);
entities.SaveChanges();
redirectToAction("Index");
}
В-четвертых, фрагмент представления:
<p>
<label for="Name">Name:</label>
<%= Html.TextBox("Name", Model.Pers.Name) %>
<%= Html.ValidationMessage("Name", "*") %>
</p>
<p>
<label for="DepartmentId">Family:</label>
<%= Html.DropDownList("DepartmentId", Model.Departments)%>
<%= Html.ValidationMessage("DepartmentId", "*")%>
</p>
Литература:
Ответ 3
Я не нашел, что готовый помощник DropDownList отлично работает с привязкой к модели. Мне всегда приходится извлекать значение DropDownList из ViewData и сопоставлять значение вручную в моем контроллере до того, как он внесет изменения в репозиторий или db.
В большинстве случаев я использую DropDownList, чтобы показывать параметры для пользовательского типа отношений (например, отделы для человека). MVC не может знать, как сопоставить объект просто на основе значения выбранного элемента в списке. Скорее, вам нужно захватить эту сущность, используя выбранное значение и нарисовать ее самостоятельно. Я не знаю, была ли эта проблема здесь, и я не пробовал модель привязки списка к модели с просто простыми типами в качестве опций (например, количество продуктов для покупки или что-то в этом роде), но мне было бы любопытно узнать больше об этой конкретной проблеме и о том, как другие управляют привязкой модели к выпадающим спискам.