¿Deberían los repositorios devolver IQueryable?

59

He estado viendo muchos proyectos que tienen repositorios que devuelven instancias de IQueryable . Esto permite que se puedan realizar filtros adicionales y la clasificación se puede realizar en el IQueryable por otro código, lo que se traduce en la generación de un SQL diferente. Tengo curiosidad por saber de dónde viene este patrón y si es una buena idea.

Mi mayor preocupación es que un IQueryable es una promesa de llegar a la base de datos algún tiempo después, cuando se enumera. Esto significa que un error se lanzaría fuera del repositorio. Esto podría significar que se lanza una excepción de Entity Framework en una capa diferente de la aplicación.

En el pasado, también me he encontrado con problemas con varios Conjuntos de resultados activos (MARS) (especialmente cuando se usan transacciones) y este enfoque parece que haría que esto suceda con más frecuencia.

Siempre he llamado AsEnumerable o ToArray al final de cada una de mis expresiones LINQ para asegurarme de que la base de datos se encuentre en el lugar antes de abandonar el código del repositorio.

Me pregunto si devolver IQueryable podría ser útil como un bloque de construcción para una capa de datos. He visto un código bastante extravagante con un repositorio llamando a otro repositorio para construir un IQueryable aún mayor.

    
pregunta Travis Parks 26.03.2013 - 21:57

9 respuestas

42

Devolver IQueryable definitivamente ofrecerá más flexibilidad a los consumidores del repositorio. Pone la responsabilidad de reducir los resultados al cliente, lo que, naturalmente, puede ser tanto un beneficio como una muleta.

En el lado bueno, no necesitará crear toneladas de métodos de repositorio (al menos en esta capa) - GetAllActiveItems, GetAllNonActiveItems, etc. - para obtener los datos que desea. Dependiendo de su preferencia, de nuevo, esto podría ser bueno o malo. Usted deberá (/ debería) definir los contratos de comportamiento a los que se adhieren sus implementaciones, pero eso depende de usted.

Por lo tanto, podría poner la lógica de recuperación arenosa fuera del repositorio y dejar que se use como el usuario quiera. Por lo tanto, exponer a IQueryable brinda la mayor flexibilidad y permite realizar consultas eficientes en lugar de filtros en la memoria, etc., y podría reducir la necesidad de hacer un montón de métodos específicos de obtención de datos.

Por otro lado, ahora le has dado a tus usuarios una escopeta. Pueden hacer cosas que quizás no pretendía (abusar de .include (), realizar consultas pesadas y filtrar en memoria en sus respectivas implementaciones, etc.), lo que básicamente haría a un lado la capa y controles de comportamiento porque has dado acceso completo.

Entonces, dependiendo del equipo, su experiencia, el tamaño de la aplicación, las capas y la arquitectura en general ... depende: - \

    
respondido por el hanzolo 26.03.2013 - 22:34
26

La exposición de IQueryable a las interfaces públicas no es una buena práctica. La razón es fundamental: no puede proporcionar la realización de IQueryable como se dice que es.

La interfaz pública es un contrato entre el proveedor y los clientes. Y en la mayoría de los casos existe la expectativa de una implementación total. IQueryable es un tramposo:

  1. IQueryable es casi imposible de implementar. Y actualmente no hay una solución adecuada. Incluso Entity Framework tiene un montón de UnsupportedExceptions .
  2. Hoy en día, los proveedores de Queryable dependen en gran medida de la infraestructura. Sharepoint tiene su propia implementación parcial y el proveedor My SQL tiene una implementación parcial distinta.

Patrón de repositorio nos da una representación clara por capas y, lo más importante, independencia de realización. Así que podemos cambiar en la futura fuente de datos.

La pregunta es " ¿Debería haber una posibilidad de sustitución de la fuente de datos? ".

Si mañana parte de los datos se puede mover a los servicios de SAP, a la base de datos de NoSQL, o simplemente a archivos de texto, ¿podemos garantizar la implementación adecuada de la interfaz IQueryable?

( más puntos positivos sobre por qué esto es una mala práctica)

    
respondido por el Artru 16.12.2013 - 20:40
18

De manera realista, tiene tres alternativas si desea una ejecución diferida:

  • Hazlo de esta manera: expone un IQueryable .
  • Implemente una estructura que exponga métodos específicos para filtros o "preguntas" específicos. ( GetCustomersInAlaskaWithChildren , abajo)
  • Implemente una estructura que exponga una API de filtro / clasificación / página fuertemente tipificada y genere el IQueryable internamente.

Prefiero el tercero (aunque he ayudado a los colegas a implementar el segundo), pero obviamente hay algo de configuración y mantenimiento involucrados. (T4 al rescate!)

Editar : para aclarar la confusión sobre lo que estoy hablando, considere el siguiente ejemplo robado de IQueryable puede matar a tu perro , Roba a tu esposa, mata tu voluntad de vivir, etc.

En el escenario en el que expondrías algo como esto:

public class CustomerRepo : IRepo 
{ 
     private DataContext ct; 
     public Customer GetCustomerById(int id) { ... } 
     public Customer[] GetCustomersInAlaskaWithChildren() { ... } 
} 

la API de la que estoy hablando le permitiría exponer un método que le permitiría expresar GetCustomersInAlaskaWithChildren (o cualquier otra combinación de criterios) de manera flexible, y el repositorio lo ejecutaría como un IQueryable y devolvería los resultados para ti. Pero lo importante es que se ejecuta dentro de la capa del repositorio, por lo que aún aprovecha la ejecución diferida. Una vez que recupere los resultados, aún puede hacer LINQ-to-objects en el contenido de su corazón.

Otra ventaja de un enfoque como este es que, debido a que son clases POCO, este repositorio puede vivir detrás de un servicio web o WCF; puede estar expuesto a AJAX u otras personas que llaman que no saben lo primero acerca de LINQ.

    
respondido por el GalacticCowboy 26.03.2013 - 22:28
8

En realidad, solo hay una respuesta legítima: depende de cómo se utilizará el repositorio.

En un extremo, su repositorio es una envoltura muy delgada alrededor de un DBContext, por lo que puede inyectar una apariencia de comprobabilidad alrededor de una aplicación basada en bases de datos. Realmente no hay una expectativa del mundo real de que esto se pueda usar de manera desconectada sin un DB amigable con LINQ detrás porque su aplicación CRUD nunca lo necesitará. Entonces claro, ¿por qué no usar IQueryable? Probablemente preferiría IEnumarable ya que obtiene la mayoría de los beneficios [lea: ejecución retrasada] y no se siente tan sucio.

A menos que esté sentado en ese extremo, me esforzaría por aprovechar el espíritu del patrón del repositorio y devolver las colecciones materializadas adecuadas que no tienen una implicación de una conexión de base de datos subyacente.

    
respondido por el Wyatt Barnett 26.03.2013 - 22:23
5
 > Should Repositories return IQueryable?

NO si desea realizar pruebas / desarrollo guiado por pruebas porque no hay una manera fácil de crear un repositorio simulado para probar su businesslogic (= repositorio-consumidor) aislado de la base de datos u otro proveedor de IQueryable.

    
respondido por el k3b 21.01.2014 - 18:00
5

Desde un punto de vista de arquitectura pura, IQueryable es una abstracción con fugas. Como una generalidad, proporciona al usuario demasiada potencia. Dicho esto, hay lugares donde tiene sentido. Si está utilizando OData, IQueryable hace que sea extremadamente fácil proporcionar un punto final que sea fácilmente filtrable, clasificable, agrupable ... etc. De hecho, prefiero crear DTO que mis entidades asignan y IQueryable devuelve el DTO en su lugar. De esa manera, prefiero filtrar mis datos y realmente solo uso el método IQueryable para permitir la personalización de los resultados por parte del cliente.

    
respondido por el Slick86 17.06.2015 - 21:42
4

Creo que la exposición de IQueryable en su repositorio es perfectamente aceptable durante las fases iniciales de desarrollo. Existen herramientas de IU que pueden trabajar con IQueryable directamente y manejar la clasificación, el filtrado, la agrupación, etc.

Dicho esto, creo que simplemente exponer crud en el repositorio y llamarlo un día es irresponsable. Tener operaciones útiles y ayudantes de consultas para consultas comunes le permite centralizar la lógica de una consulta y al mismo tiempo exponer una superficie de interfaz más pequeña a los consumidores del repositorio.

Para las primeras iteraciones de un proyecto, la exposición pública de IQueryable en el repositorio permite un crecimiento más rápido. Antes de la primera versión completa, recomiendo que la operación de consulta sea privada o protegida y que las consultas descabelladas estén bajo el mismo techo.

    
respondido por el Michael Brown 26.03.2013 - 22:33
4

Lo que he visto hecho (y lo que estoy implementando) es lo siguiente:

public interface IEntity<TKey>
{
    TKey Id { get; set; }
}

public interface IRepository<TEntity, in TKey> where TEntity : IEntity<TKey>
{
    void Create(TEntity entity);
    TEntity Get(TKey key);
    IEnumerable<TEntity> GetAll();
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> expression);
    void Update(TEntity entity);
    void Delete(TKey id);
}

Lo que esto te permite hacer es tener la flexibilidad de hacer una consulta IQueryable.Where(a => a.SomeFilter) en tu repositorio haciendo concreteRepo.GetAll(a => a.SomeFilter) sin exponer ningún negocio de LINQy.

    
respondido por el dav_i 21.01.2014 - 17:03
4

También me he enfrentado a esa elección.

Entonces, resumamos los lados positivos y negativos:

Positivos:

  • Flexibilidad. Definitivamente, es flexible y conveniente para permitir que el lado del cliente cree consultas personalizadas con un solo método de repositorio

Negatives:

  • Untestable . (en realidad probará la implementación subyacente de IQueryable, que generalmente es su ORM. Pero debería probar su lógica en su lugar)
  • Desenfoca la responsabilidad . Es imposible decir lo que hace ese método particular de su repositorio. En realidad, es un método divino, que puede devolver cualquier cosa que desee en toda su base de datos
  • Inseguro . Sin restricciones en lo que puede ser consultado por este método de repositorio, en algunos casos, tales implementaciones pueden ser inseguras desde el punto de seguridad.
  • Problemas de almacenamiento en caché (o, para ser genéricos, cualquier problema previo o posterior al procesamiento). Por lo general, es imprudente, por ejemplo, almacenar en caché todo en su aplicación. Intentarás almacenar en caché algo, lo que causará una carga significativa en tu base de datos. Pero, ¿cómo hacer eso, si solo tiene un método de repositorio, que los clientes utilizan para realizar prácticamente cualquier consulta en la base de datos?
  • Problemas de registro . Registrar algo en la capa del repositorio le hará inspeccionar IQueryable primero para verificar si esta llamada en particular se registra o no.

Yo recomendaría no usar este enfoque. Considere, en cambio, crear varias Especificaciones e implemente métodos de repositorio, que puedan consultar la base de datos utilizando esas. Patrón de especificación es una excelente manera de resumir un conjunto de condiciones.

    
respondido por el Vladislav Rastrusny 06.11.2015 - 15:54

Lea otras preguntas en las etiquetas