c# - generic - Patrón de repositorio con Entity Framework 4.1 y relaciones entre padres e hijos
repository pattern c# entity framework (1)
La razón principal por la que deseo utilizar este patrón es evitar llamar operaciones de acceso a datos específicos de EF 4.1 desde el dominio. Prefiero llamar operaciones CRUD genéricas desde una interfaz IRepository. Esto hará que las pruebas sean más fáciles
No , no hará que tus pruebas sean más fáciles . Expuso IQueryable
por lo que su repositorio no se puede probar por unidad .
si alguna vez tengo que cambiar el marco de acceso a datos en el futuro, podré hacerlo sin refactorizar una gran cantidad de código.
No, tendrás que cambiar mucho el código de todos modos porque has expuesto IQueryable
y porque EF / ORM es una abstracción con goteras: tu capa superior espera que algún comportamiento ocurra mágicamente dentro de tu ORM (por ejemplo, carga lenta). También esta es una de las razones más extrañas para ir al repositorio. Simplemente elija la tecnología adecuada ahora y úsela para hacer las apuestas. Si tiene que cambiarlo más tarde significa que ha cometido un error y ha elegido el incorrecto o que los requisitos han cambiado ; en cualquier caso, será mucho trabajo.
Pero esto no funciona porque EF piensa que la persona es nueva, y tratará de volver a INSERTAR la persona en la base de datos, lo que causará un error de restricción de clave principal.
Sí, porque está utilizando un nuevo contexto para cada repositorio = ese es un enfoque equivocado. Los repositorios deben compartir el contexto. Su segunda solución tampoco es correcta porque devuelve su dependencia EF a la aplicación; el repositorio expone el contexto. Esto generalmente se resuelve por segundo patrón - unidad de trabajo. La unidad de trabajo envuelve el contexto y la unidad de formas de trabajo el conjunto de cambios atómicos. SaveChanges
debe estar expuesto en la unidad de trabajo para confirmar los cambios realizados por todos los repositorios relacionados.
Ahora tengo un problema con el grupo ACTUALIZADO en la base de datos cada vez que quiero crear un mapa de grupo / persona.
¿Por qué cambias el estado? Recibió la entidad del repositorio, por lo que hasta que la haya desconectado no hay ninguna razón para llamar a Attach
y cambiar el estado manualmente. Todo esto debería suceder automáticamente en la entidad adjunta. Simplemente llame a SaveChanges
. Si está utilizando entidades separadas, entonces debe establecer correctamente el estado para cada entidad y relación, de modo que en ese caso necesitará algunas sobrecargas lógicas o de actualización para manejar todos los escenarios.
¿Me estoy acercando a esto de la manera correcta? Mi Repositorio está empezando a verse EF céntrico cuando empiezo a trabajar con EF y el patrón de repositorio.
No lo creo. En primer lugar, no estás usando raíces agregadas. Si lo hace, inmediatamente encontrará que el repositorio genérico no es adecuado para eso. El repositorio para raíces agregadas tiene métodos específicos por raíz agregada para manejar el trabajo con relaciones agregadas por la raíz. Group
no forma parte del agregado de Person
, pero GroupPersonMap
debe serlo para que el repositorio de Person tenga métodos específicos para manejar la adición y eliminación de grupos de una persona (pero no para crear o eliminar grupos). El repositorio genérico de Imo es una capa redundante .
Todavía tengo cierta confusión con el patrón de repositorio. La razón principal por la que deseo utilizar este patrón es evitar llamar operaciones de acceso a datos específicos de EF 4.1 desde el dominio. Prefiero llamar operaciones CRUD genéricas desde una interfaz IRepository. Esto facilitará las pruebas y si alguna vez tengo que cambiar el marco de acceso a datos en el futuro, podré hacerlo sin refactorizar una gran cantidad de código.
Aquí hay un ejemplo de mi situación:
Tengo 3 tablas en la base de datos: Group
, Person
y GroupPersonMap
. GroupPersonMap
es una tabla de enlaces y solo consiste en las claves principales de Group
y Person
. Creé un modelo EF de las 3 tablas con el diseñador de VS 2010. EF fue lo suficientemente inteligente como para suponer que GroupPersonMap
es una tabla de enlace, por lo que no se muestra en el diseñador. Quiero utilizar mis objetos de dominio existentes en lugar de las clases generadas por EF, así que desactivo la generación de código para el modelo.
Mis clases existentes que coinciden con el modelo EF son las siguientes:
public class Group
{
public int GroupId { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> People { get; set; }
}
public class Person
{
public int PersonId {get; set; }
public string FirstName { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
Tengo una interfaz de depósito genérica como esta:
public interface IRepository<T> where T: class
{
IQueryable<T> GetAll();
T Add(T entity);
T Update(T entity);
void Delete(T entity);
void Save()
}
y un repositorio genérico de EF:
public class EF4Repository<T> : IRepository<T> where T: class
{
public DbContext Context { get; private set; }
private DbSet<T> _dbSet;
public EF4Repository(string connectionString)
{
Context = new DbContext(connectionString);
_dbSet = Context.Set<T>();
}
public EF4Repository(DbContext context)
{
Context = context;
_dbSet = Context.Set<T>();
}
public IQueryable<T> GetAll()
{
// code
}
public T Insert(T entity)
{
// code
}
public T Update(T entity)
{
Context.Entry(entity).State = System.Data.EntityState.Modified;
Context.SaveChanges();
}
public void Delete(T entity)
{
// code
}
public void Save()
{
// code
}
}
Ahora supongamos que solo quiero asignar un Group
existente a una Person
existente. Tendría que hacer algo como lo siguiente:
EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString");
EFRepository<Person> personRepository = new EFRepository<Person>("name=connString");
var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First();
var person = personRepository.GetAll().Where(p => p.PersonId == 2).First();
group.People.Add(person);
groupRepository.Update(group);
Pero esto no funciona porque EF cree que Person
es nueva, y tratará de volver a INSERT
la Person
en la base de datos, lo que causará un error de restricción de clave primaria. Debo usar el método Attach
DbSet
para decirle a EF que la Person
ya existe en la base de datos, así que simplemente cree un mapa entre Group
y Person
en la tabla GroupPersonMap
.
Entonces, para adjuntar Person
al contexto, ahora debo agregar un método Attach
a mi IRepository:
public interface IRepository<T> where T: class
{
// existing methods
T Attach(T entity);
}
Para corregir el error de restricción de clave principal:
EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString");
EFRepository<Person> personRepository = new EFRepository<Person>(groupRepository.Context);
var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First();
var person = personRepository.GetAll().Where(p => p.PersonId == 2).First();
personRepository.Attach(person);
group.People.Add(person);
groupRepository.Update(group);
Fijo. Ahora tengo que lidiar con otro problema en el que el Group
se está ACTUALIZANDO en la base de datos cada vez que creo un mapa de grupo / persona. Esto se debe a que en mi método EFRepository.Update()
, el estado de la entidad se establece explícitamente como Modified''. I must set the Group''s state to
Modified''. I must set the Group''s state to
Sin cambios so the
tabla del grupo no se modifique.
Para solucionarlo, debo agregar algún tipo de sobrecarga de Update
a mi IRepository que no actualice la entidad raíz, o Group
, en este caso:
public interface IRepository<T> where T: class
{
// existing methods
T Update(T entity, bool updateRootEntity);
}
La implementación EF4 del método Update se vería así:
T Update(T entity, bool updateRootEntity)
{
if (updateRootEntity)
Context.Entry(entity).State = System.Data.EntityState.Modified;
else
Context.Entry(entity).State = System.Data.EntityState.Unchanged;
Context.SaveChanges();
}
Mi pregunta es: ¿Me estoy acercando a esto de la manera correcta? Mi Repositorio está empezando a verse EF céntrico cuando empiezo a trabajar con EF y el patrón de repositorio. Gracias por leer esta larga publicación