Patrón de repositorio con Entity Framework 4.1 y Relaciones Padre / Hijo


Todavía tengo cierta confusión con el Patrón del Repositorio. La razón principal por la que quiero usar este patrón es evitar llamar a 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 y si alguna vez tengo que cambiar el marco de acceso a datos en el futuro, podré hacerlo sin refactorizar mucho código.

He Aquí 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 primarias Group y Person. Creé un modelo EF de las 3 tablas con VS 2010 designer. EF fue lo suficientemente inteligente como para asumir que GroupPersonMap es una tabla de enlaces para que no se muestre en el diseñador. Quiero usar mis objetos de dominio existentes en lugar de las clases generadas por EF, por lo 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; }
}

I tener una interfaz de repositorio 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 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 mapear un Group existente a un 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 piensa que Person es nuevo, e intentará re-INSERT el Person en la base de datos, lo que causará un error de restricción de clave primaria. Debo usar DbSet ' s Attach método para decirle a EF que el Person ya existe en el base de datos así que simplemente cree un mapa entre Group y Person en la tabla GroupPersonMap.

Así que 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 primaria:

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);

Arreglado. Ahora tengo que lidiar con otro problema donde Group se actualiza 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 en Modified'. I must set the Group's state to Sin cambiosso theGrupo` la tabla no se modifica.

Para arreglar esto debo agregar algún tipo de Update sobrecarga a mi IRepository que no 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ón EF4 del método Update se vería algo como esto:

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 abordando esto de la manera correcta? Mi repositorio está empezando a verse centrado en EF a medida que empiezo a trabajar con EF y el patrón de repositorio. Gracias por leer este largo post

Author: jodev, 2011-08-24

1 answers

La razón principal por la que quiero usar este patrón es para evitar llamar EF 4.1 operaciones de acceso a datos específicos desde el dominio. Preferiría llama a operaciones CRUD genéricas desde una interfaz IRepository. Esta voluntad facilitar las pruebas

No, no hará que sus pruebas sean más fáciles. Has expuesto IQueryable así que su repositorio no es comprobable por unidad.

Si alguna vez tengo que cambiar el marco de acceso a datos en el futuro, ser ser capaz de hacerlo sin refactorizar mucho código.

No Tendrá que cambiar mucho código de todos modos porque expuso IQueryable y porque EF / E es una abstracción con fugas: su capa superior espera que algún comportamiento ocurra mágicamente dentro de su OR (por ejemplo, carga lenta). También esta es una de las razones más extrañas para ir por repositorio. Simplemente elija la tecnología adecuada ahora y úsela para obtener las apuestas de la misma. Si tienes que cambiarlo más tarde significa que hiciste un error y eligió el equivocado o 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 vuelva a INSERTAR a la Persona en la base de datos, lo que causará una clave primaria error de restricción.

Sí porque está utilizando un nuevo contexto para cada repositorio = es un enfoque incorrecto. Los repositorios deben compartir el contexto. Su segunda solución no es correcta también porque usted pone su dependencia de EF al repositorio de aplicaciones está exponiendo el contexto. Esto se resuelve generalmente por segundo patrón - unidad de trabajo. Unit of work envuelve el contexto y unit of work forma el conjunto de cambios atómicos - SaveChanges debe estar expuesto en unit of work para confirmar los cambios realizados por todos los repositorios relacionados.

Ahora tengo un problema con el Grupo que se ACTUALIZA en la base de datos cada vez que quiero crear un mapa de Grupo/Persona.

¿Por qué cambiar la estado? Ha recibido la entidad del repositorio, por lo que hasta que la separe no hay 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 la relación, por lo que en tal caso necesitará alguna lógica o sobrecargas de actualización para manejar todos los escenarios.

¿Me estoy acercando a esto de la manera correcta? Mi Repositorio es empezando a mirar EF centric a medida que 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 es parte de Person aggregate, pero GroupPersonMap debería serlo, por lo que su repositorio de Personas debería tener métodos específicos para manejar la adición y eliminación de grupos de persona (pero no para crear o eliminar grupos ellos mismos). El repositorio genérico Imo es capa redundante.

 66
Author: Ladislav Mrnka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:07