12

Usar LINQ TO SQL como base de una solución basada en repositorio. Mi aplicación es el siguiente:Cargando subregionales en el patrón de repositorio

IRepository

FindAll 
FindByID 
Insert 
Update 
Delete 

Entonces tengo métodos de extensión que se utilizan para consultar los resultados como tales:

WhereSomethingEqualsTrue() ... 

Mi pregunta es la siguiente:

El repositorio de Mis usuarios tiene N roles. ¿Creé un repositorio Roles para administrar Roles? Me preocupa que termine creando decenas de repositorios (1 por mesa casi a excepción de las tablas de unión) si tomo esta ruta. Es común un repositorio por mesa?

Respuesta

31

Si está creando su Repositorio para ser específico de una Entidad (tabla), de modo que cada Entidad tenga la lista de métodos en su interfaz de IRepository que mencionó anteriormente, entonces lo que realmente está haciendo es una implementación de Active Record patrón.

Usted debe definitivamente no tener un repositorio por mesa. Debe identificar los agregados en su modelo de dominio y las operaciones que desea realizar en ellos. Los usuarios y roles suelen estar estrechamente relacionados y, en general, su aplicación realizaría operaciones con ellos conjuntamente. Esto requiere un repositorio único, centrado en el usuario y su conjunto de entidades estrechamente relacionadas.

Supongo por su publicación que tiene seen this example. El problema con este ejemplo es que todos los repositorios comparten la misma funcionalidad CRUD en el nivel base, pero no va más allá y no implementa ninguna de las funciones de dominio. Todos los repositorios en ese ejemplo tienen el mismo aspecto, pero en realidad, los repositorios reales no tienen el mismo aspecto (aunque todavía deben estar interconectados), habrá operaciones de dominio específicas asociadas con cada uno.

Sus operaciones dominio del repositorio deben mirar más como:

userRepository.FindRolesByUserId(int userID) 
userRepository.AddUserToRole(int userID) 
userRepository.FindAllUsers() 
userRepository.FindAllRoles() 
userRepository.GetUserSettings(int userID) 

etc ...

Se trata de operaciones específicas que su aplicación quiere realizar en los datos subyacentes, y el repositorio deben proporcionar eso. Piénselo ya que el Repositorio representa el conjunto de operaciones atómicas que realizaría en el dominio. Si elige compartir alguna funcionalidad a través de un repositorio genérico y ampliar los repositorios específicos con métodos de extensión, ese es un enfoque que puede funcionar perfectamente para su aplicación.

Una buena regla general es que debe ser raro para que su aplicación necesite crear instancias de varios repositorios para completar una operación. La necesidad surge, pero si cada controlador de eventos en su aplicación está haciendo malabares con seis repositorios solo para tomar la entrada del usuario y crear una instancia correcta de las entidades que representa la entrada, entonces es probable que tenga problemas de diseño.

+1

Bueno, ahí tienes. Aprende algo todos los días. Tendré que ajustar mi propio enfoque :-) – Dylan

+0

Sí. También aprendo algo con esta respuesta. Sabía que era lógico tener varios repositorios, pero no sabía que cada repositorio debería construirse alrededor de agregados. Tiene sentido total –

+0

Excelente publicación. –

4

¿Es común un repositorio por mesa?

No, pero aún puede tener varios depósitos. Debe construir un repositorio alrededor de un agregado.

Además, es posible que pueda abstraer algunas funciones de todos los repositorios ... y, dado que está utilizando LINQ to SQL, es probable que pueda ...

Se puede implementar un repositorio de base que de una manera genérica implementa toda esta funcionalidad común.

El siguiente ejemplo sirve solo para demostrar este punto. Probablemente necesite una gran mejora ...

interface IRepository<T> : IDisposable where T : class 
    { 
     IEnumerable<T> FindAll(Func<T, bool> predicate); 
     T FindByID(Func<T, bool> predicate); 
     void Insert(T e); 
     void Update(T e); 
     void Delete(T e); 
    } 

    class MyRepository<T> : IRepository<T> where T : class 
    { 
     public DataContext Context { get; set; } 

     public MyRepository(DataContext context) 
     { 
      Context = Context; 
     } 

     public IEnumerable<T> FindAll(Func<T,bool> predicate) 
     { 
      return Context.GetTable<T>().Where(predicate); 
     } 

     public T FindByID(Func<T,bool> predicate) 
     { 
      return Context.GetTable<T>().SingleOrDefault(predicate); 
     } 

     public void Insert(T e) 
     { 
      Context.GetTable<T>().InsertOnSubmit(e); 
     } 

     public void Update(T e) 
     { 
      throw new NotImplementedException(); 
     } 

     public void Delete(T e) 
     { 
      Context.GetTable<T>().DeleteOnSubmit(e); 
     } 

     public void Dispose() 
     { 
      Context.Dispose(); 
     } 
    } 
+2

No, cada repositorio debe encapsular la lógica de persistencia para una raíz agregada –

+0

@Johannes Sí, pero ¿cómo? –

+0

¿Puede indicar cómo la clase de cliente utiliza la función "T FindByID (Func predicado)". Estoy pidiendo una demo del código. – Lijo

1

Para mí, el patrón de repositorio consiste en poner una capa delgada alrededor de su metodología de acceso a datos. LINQ to SQL en su caso, pero NHibernate, hecho a mano en otros. Lo que me he encontrado haciendo es crear un repositorio por tabla, porque es extremadamente simple (como las listas de bruno y ya lo tienes). Esa es la responsable de encontrar cosas y hacer operaciones CRUD.

Pero luego tengo un nivel de servicio que se ocupa más de las raíces agregadas, como menciona Johannes. Tendría un UserService con un método como GetExistingUser (int id). Esto llamaría internamente al método UserRepository.GetById() para recuperar al usuario. Si su proceso empresarial requiere que la clase de usuario devuelta por GetExistingUser() casi siempre necesite que se llene la propiedad User.IsInRoles(), simplemente haga que UserService dependa del UserRepository y RoleRepository. En pseudo código podría ser algo como esto:

public class UserService 
{ 
    public UserService(IUserRepository userRep, IRoleRepository roleRep) {...} 
    public User GetById(int id) 
    { 
     User user = _userService.GetById(id); 
     user.Roles = _roleService.FindByUser(id); 
     return user; 
} 

El userRep y roleRep se construirían con su LINQ a SQL bits de algo como esto:

public class UserRep : IUserRepository 
{ 
    public UserRep(string connectionStringName) 
    { 
     // user the conn when building your datacontext 
    } 

    public User GetById(int id) 
    { 
     var context = new DataContext(_conString); 
     // obviously typing this freeform but you get the idea... 
     var user = // linq stuff 
     return user; 
    } 

    public IQueryable<User> FindAll() 
    { 
     var context = // ... same pattern, delayed execution 
    } 
} 

Personalmente haría que las clases de repositorios de ámbito interno y tiene el UserService y otras clases XXXXXService públicas, así que mantenga honestos a los consumidores de la API del servicio. Así que, de nuevo, veo que los repositorios están más estrechamente relacionados con el hecho de hablar con un almacén de datos, pero su capa de servicio está más alineada con las necesidades de su proceso comercial.

A menudo me he encontrado pensando demasiado en la flexibilidad de Linq para objetos y todo eso y usando IQuerable y otros en lugar de simplemente crear métodos de servicio que escuchan lo que realmente necesito.User LINQ donde sea apropiado, pero no intente hacer que el repositorio haga todo.

public IList<User> ActiveUsersInRole(Role role) 
{ 
    var users = _userRep.FindAll(); // IQueryable<User>() - delayed execution; 
    var activeUsersInRole = from users u where u.IsActive = true && u.Role.Contains(role); 
    // I can't remember any linq and i'm type pseudocode, but 
    // again the point is that the service is presenting a simple 
    // interface and delegating responsibility to 
    // the repository with it's simple methods. 
    return activeUsersInRole; 
} 

Así que, eso fue un poco divagante. No estoy seguro de si realmente ayudé a alguno, pero mi consejo es evitar ser demasiado sofisticado con los métodos de extensión, y simplemente agregar otra capa para mantener cada una de las partes móviles bastante simple. Funciona para mi.

1

Si escribimos nuestra capa de repositorio tan detallada como sugiere Womp, ¿qué ponemos en nuestra capa de servicio? ¿Tenemos que repetir las mismas llamadas a métodos, que consistirían principalmente en llamadas al método de repositorio correspondiente, para usar en nuestros controladores o códigos subyacentes? Esto supone que tiene una capa de servicio, donde escribe su validación, almacenamiento en caché, flujo de trabajo, código de autenticación/autorización, ¿verdad? ¿O estoy fuera de la base?

Cuestiones relacionadas