nhibernate - manualmente - llenar dropdownlist c# con list
Enlace de modelo ASP.NET MVC 2 RC con NHibernate y listas desplegables (3)
Tengo un problema con el enlace del modelo en mi aplicación ASP.NET MVC 2 RC que utiliza NHibernate para el acceso a datos. Estamos tratando de construir la aplicación de una manera Ruby on Rails y tenemos una arquitectura muy simple donde las entidades de dominio se usan desde la base de datos hasta la vista.
La aplicación tiene un par de entidades de dominio que se pueden ilustrar con las dos clases siguientes:
public class Product {
...
public Category Category { get; set; }
}
public class Category {
public int Id { get; set; }
public string Name { get; set; }
}
En la vista que hace que el formulario de edición tenga la siguiente declaración para mostrar una lista desplegable:
<%= Html.DropDownListFor(model => model.Category.Id,
new SelectList(ViewData["categories"] as IList<Category>, "Id", "Name"),
"-- Select Category --" ) %>
No tenga en cuenta el uso de datos de vista "sin escritura" para mantener la colección de categorías.
El método de acción que recibe la publicación del formulario es similar al siguiente. Tenga en cuenta que el atributo TransactionFilter agrega gestión de transacciones NHibernate y confirma la transacción si no se producen excepciones y la validación tiene éxito.
[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);
}
Mi problema es que el producto.Category.Id se establece con el valor seleccionado en el formulario, por ejemplo, Category.Id = "2". El uso de la agrupación de modelo predeterminada da como resultado el siguiente tipo de excepción de NHibernate:
identifier of an instance of Name.Space.Entities.Category was altered from 4 to 2
Eso tiene mucho sentido ya que el producto ya tiene una categoría asignada y solo se está cambiando la clave principal de esa categoría existente. Otra instancia de categoría debería haber sido asignada en su lugar.
Supongo que se puede crear un ModelBinder personalizado para manejar este problema, pero ¿hay alguna forma más sencilla de hacerlo funcionar? ¿Pueden (y deberían) las entidades de dominio modificarse para manejar esto?
He usado técnicas similares con las clases de SQL de Linq sin problemas. No creo que necesites un ModelBinder personalizado para esto. UpdateModel debe actualizar la clase de Producto que está ingresando en ella, no la subclase Category adjunta. Compruebe el html generado por DropDownListFor helper. ¿Cuál es el nombre del elemento? Debería ser el nombre del campo de clave foránea en su tabla Productos (por ejemplo, "ID de categoría" o "ID de Categoría de producto", no "Categoría.ID"). Si es "Category.Id": intente cambiar el primer parámetro de DropDownListFor a "model => model.Category" o "model => model.CategoryID" (o cualquiera que sea el campo de clave foránea). Esto debería hacer que UpdateModel solo actualice el campo de clave foránea en la clase Product y no el ID de clase Category.
La solución que elegimos en ese momento era algo similar a esto:
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;
}
Simplemente le decimos a la carpeta del modelo que excluya la propiedad de la asociación y la maneje manualmente. No es bonito, pero funcionó en el momento ...
Resolví un problema similar con cuadros combinados en mi página de edición cambiando la siguiente línea
@Html.DropDownListFor(x => x.Employee.Id, new SelectList(ViewBag.Employees, "Id", "DisplayName"))
por
@Html.DropDownListFor(x => x.Employee, new SelectList(ViewBag.Employees, "Id", "DisplayName"))
Así que eliminé el ''.Id'' como sugirió Bryan. Antes del cambio, el modelo solo contenía el ID del empleado y luego del cambio, el Binder también cargaba todos los detalles del empleado en el modelo.