35

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.Patrón de repositorio con Entity Framework 4.1 y relaciones padre/hijo

Aquí es un ejemplo de mi situación:

Tengo 3 tablas en la base de datos: Group, Person y GroupPersonMap. GroupPersonMap es una tabla de enlace y solo consiste en las claves primarias 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 asumir 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 existente que coincida con el modelo de EF son los 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; } 
} 

que tienen una interfaz genérica repositorio de este modo:

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 EF genérica:

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 unexistente. 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 funcionará porque piensa EF Person es nuevo, y voy a tratar de volver a la INSERTPerson en la base de datos que provocará un error de restricción de clave primaria . Debo utilizar Attach método DbSet 's para decir que el EF Person ya existe en la base de datos por lo que acaba de crear un mapa entre Group y Person en la tabla GroupPersonMap.

Así que el fin de unir a Person el contexto ahora debo añadir un método Attach a mi IRepository:

public interface IRepository<T> where T: class 
{ 
    // existing methods 
    T Attach(T entity); 
} 

Para solucionar el error de clave principal limitación:

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 donde Group se está ACTUALIZANDO en la base de datos cada vez que creo un mapa de Grupo/Persona. Esto es porque en mi método EFRepository.Update(), el estado de la entidad se establece explícitamente en Modified'. I must set the Group's state to Sin cambios so the La tabla del grupo `no se modifica.

Para solucionar esto, debo añadir algún tipo de Update sobrecarga a mi IRepository que no se actualiza 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ó EF4 del método de actualización podría ser algo de esta manera:

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: ¿estoy acercando este el camino correcto? 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

Respuesta

66

The primary reason why I want to use this pattern is to avoid calling EF 4.1 specific data access operations from the domain. I'd rather call generic CRUD operations from a IRepository interface. This will make testing easier

No, will not make your testing easier. You exposed IQueryable por lo que su repositorio is not unit testable.

if I ever have to change the data access framework in the future, I will be able to do so without refactoring a lot of code.

No se tendrá que cambiar un montón de código de todos modos porque usted expuesto IQueryable y debido EF/ORM es la abstracción con fugas - su capa superior espera un comportamiento sucede mágicamente dentro de su ORM (por ejemplo, la carga diferida). 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 you did a mistake and chose the wrong one or requirements have changed - en cualquier caso será mucho trabajo.

But this doesn't work because EF thinks Person is new, and will try to re-INSERT the Person into the database which will cause a primary key constraint error.

Sí porque está utilizando un nuevo contexto para cada repositorio = ese es el 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. Unit of work wraps the context y la unidad de trabajo constituye el conjunto de cambios atómica - SaveChanges se debe exponer en la unidad de trabajo para confirmar los cambios realizados por todos los repositorios relacionados.

Now I have an issue with the Group being UPDATE'd in the database every time I want to create a Group/Person map.

¿Por qué cambias el estado? Recibió la entidad del repositorio, por lo tanto, hasta que la haya desconectado, no hay ninguna razón para llamar al Attach y cambiar el estado manualmente. Todo esto debería suceder automáticamente en la entidad adjunta. Simplemente llame al SaveChanges. Si está utilizando entidades separadas, entonces you must correctly set state for every entity y relación, entonces en tal caso necesitará algunas sobrecargas lógicas o de actualización para manejar todos los escenarios.

Am I approaching this the right way? My Repository is starting to look EF centric as I start to work with EF and the repository pattern.

No lo creo. En primer lugar, no estás usando raíces agregadas. Si lo hace, encontrará inmediatamente que generic repository 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 es parte de Person agregado, sino GroupPersonMap debe haber por lo que su repositorio persona debe tener métodos específicos para manejar agregar y quitar grupos de persona (pero no para crear o eliminar grupos a sí mismos). El repositorio genérico de Imo es redundant layer.

+4

1 Gran respuesta –

+1

1 Muy informativo –

+0

1 de marco de la entidad + unidad de trabajo. Los repositorios necesitan compartir el mismo dbcontext. Apuesto que ese es mi problema. – Jhayes2118

Cuestiones relacionadas