Entity Framework 4 - ¿Dónde colocar la lógica "ApplyCurrentValues"?
-
26-09-2019 - |
Pregunta
Estoy usando el "técnica de stub" a actualizar Mi POCO (utilizado en un contexto separado, ASP.NET MVC).
Este es el código que tengo actualmente en mi controlador (que funciona):
[HttpPost]
public ActionResult Edit(Review review)
{
Review originalReview = _userContentService.FindById(review.PostId) as Review;
var ctx = _unitOfWork as MySqlServerObjectContext;
ctx.ApplyCurrentValues("MyEntities.Posts", review);
_unitOfWork.Commit();
// ..snip - MVC stuff..
}
Como puede ver, hay un olor en código en todas partes. :)
Algunos puntos:
- Utilizo la inyección de dependencia (basada en la interfaz) básicamente para todo
- Utilizo el patrón de la unidad de trabajo para abstracto ObjectContext y proporcione persistencia en múltiples repositorios
- Actualmente mi Iunitofwork La interfaz tiene solo 1 método:
void Commit();
- Controlador tiene
IUserContentService
yIUnitOfWork
inyección por DI IUserContentService
llamadasFind
en repositorios, que usan elObjectContext
.
Estas son dos cosas que no me gustan con mi código anterior:
- No quiero lanzar el Iunitofwork como
MySqlServerObjectContext
. - No quiero que el controlador tenga que preocuparse
ApplyCurrentValues
Básicamente quiero que mi código se vea así:
[HttpPost]
public ActionResult Edit(Review review)
{
_userContentService.Update(review);
_unitOfWork.Commit();
// ..snip - MVC stuff..
}
¿Alguna idea de cómo puedo hacer eso? (o algo similar).
Ya tengo inteligencia para resolver el nombre del conjunto de entidades basado en el tipo (combinación de genéricos, pluralización), así que no se preocupe demasiado por eso.
Pero Me pregunto dónde el mejor lugar para poner ApplyCurrentValues
es? No parece apropiado ponerlo en el IUnitOfWork
interfaz, ya que esta es una preocupación de persistencia (EF). Por la misma razón, no pertenece al servicio. Si lo pongo en mi MySqlServerObjectContext
clase (tiene sentido), de dónde llamaría a esto, ya que nada directamente tiene acceso a esta clase: se inyecta a través de DI cuando algo solicita IUnitOfWork
.
¿Alguna idea?
EDITAR
Tengo una solución a continuación utilizando la técnica Stub, pero el problema es que si hubiera recuperado la entidad que estoy actualizando de antemano, lanza una excepción, indicando una entidad con esa llave ya existe.
¿Qué tiene sentido, aunque no estoy seguro de cómo puede resolver esto?
¿Necesito "comprobar si la entidad ya está adjunta, si no, adjuntarla?"
¿Puede ayudar algún experto de EF4?
EDITAR
No importa: encontré la solución, vea la respuesta a continuación.
Solución
Lo descubrí, no fue fácil, así que intentaré explicar mejor que pueda. (para aquellos a quienes les importa)
Código relevante del controlador:
// _userContentService is IUserContentService
_userContentService.Update(review);
Entonces, mi controlador llama a un método llamado Update
en IUserContentService
, pasando por el fuertemente tipo Review
objeto.
Servicio de contenido de usuario Código relevante
public void Update(Post post)
{
// _userContentRepository is IPostRepository
_userContentRepository.UpdateModel(post);
}
Entonces, mi servicio llama a un método llamado UpdateModel
en IPostRepository
, pasando por el fuertemente tipo Review
objeto.
Ahora esta es la parte difícil.
Realmente tengo Sin repositorios específicos. tengo un repositorio genérico llamó GenericRepository<T> : IRepository<T>
, que maneja Todos los diferentes repositorios.
Entonces, cuando algo solicita un IPostRepository
(que estaba haciendo mi servicio), Di le daría un GenericRepository<Post>
.
Pero ahora, le doy un PostRepository
:
public class PostRepository : GenericRepository<Post>, IPostRepository
{
public void UpdateModel(Post post)
{
var originalPost = CurrentEntitySet.SingleOrDefault(p => p.PostId == post.PostId);
Context.ApplyCurrentValues(GetEntityName<Post>(), post);
}
}
Y porque la clase derivarse de GenicRepository, hereda toda la lógica del repositorio central (encontrar, agregar, etc.).
Al principio, intenté poner eso UpdateModelo código en el GenicRepository clase en sí (y luego no hubiera necesitado este repositorio específico), pero el problema es la lógica para recuperar la entidad existente se basa en una clave de entidad específica, que el GenericRepository<T>
no sabría sobre.
Pero el resultado final es el puntadas está oculto en las profundidades de la capa de datos, y termino con un controlador realmente limpio.
EDITAR
Esta "técnica de stub" también funciona:
public void UpdateModel(Post post)
{
var stub = new Review {PostId = post.PostId};
CurrentEntitySet.Attach(stub);
Context.ApplyCurrentValues(GetEntityName<Post>(), post);
}
Pero el problema es porque Correo es abstracto, no puedo instanciar y, por lo tanto, tendría que verificar el tipo de publicación y crear trozos para cada uno tipo derivado. Realmente no es una opción.
Editar 2 (última vez)
De acuerdo, obtuve la "técnica de talón" que funcione con clases abstractas, por lo que ahora se resuelve el problema de concurrencia.
Agregué un parámetro de tipo genérico a mi UpdateModelo método y el especial nueva () restricción.
Implementación:
public void UpdateModel<T>(T post) where T : Post, new()
{
var stub = new T { PostId = post.PostId };
CurrentEntitySet.Attach(stub);
Context.ApplyCurrentValues(GetEntityName<Post>, post);
}
Interfaz:
void UpdateModel<T>(T post) where T : Post, new();
Esto me impide tener que descubrir el tipo de T manualmente, previene problemas de concurrencia y también evita un viaje adicional al DB.
Bastante maravilloso.
Editar 3 (pensé que la última vez fue la última vez)
La "técnica de tallo" anterior funciona, pero si recupero el objeto de antemano, lanza una excepción que indica una entidad con esa llave ya existe en el OSM.
¿Alguien puede aconsejar cómo manejar esto?
Editar 4 (OK, ¡esto es todo!)
Encontré la solución, gracias a esto, así que respuesta: ¿Es posible verificar si un objeto ya está conectado a un contexto de datos en el marco de entidad?
Intenté "verificar si la entidad está adjunta" usando el siguiente código:
ObjectStateEntry entry;
CurrentContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry);
Pero siempre volvió nulo, incluso a través de cuando exploré el OSM, pude ver mi entidad allí con la misma clave.
Pero este código funciona:
CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName<T>(), entity), out entry)
Tal vez porque estoy usando puro Poco, la OSM tuvo problemas para descubrir la clave de la entidad, quién sabe.
Ah, y otra cosa que agregué, para que no tenga que agregar un repositorio específico para cada entidad, creé un atributo llamado "EntityKey"(atributo de propiedad pública).
Todos los POCO deben tener 1 propiedad pública decorada con ese atributo, o arrojo una excepción en mi módulo de repositorio.
Entonces, mi repositorio genérico busca esta propiedad para crear/configurar el stub.
Sí, usa la reflexión, pero es una reflexión inteligente (basada en atributos) y ya estoy usando la reflexión para la plularización de los nombres de los conjuntos de entidades de T.
De todos modos, problema resuelto, ¡todo funcionan bien ahora!