He estado viendo muchos proyectos que tienen repositorios que devuelven instancias de IQueryable
. Esto permite filtros adicionales y la clasificación se puede realizar en IQueryable
otro código, lo que se traduce en diferentes SQL que se generan. Tengo curiosidad de dónde vino este patrón y si es una buena idea.
Mi mayor preocupación es que IQueryable
es una promesa de llegar a la base de datos algún tiempo después, cuando se enumera. Esto significa que se arrojaría un error fuera del repositorio. Esto podría significar que se lanza una excepción de Entity Framework en una capa diferente de la aplicación.
También me he encontrado con problemas con los Conjuntos de resultados activos múltiples (MARS) en el pasado (especialmente cuando se usan transacciones) y parece que este enfoque llevaría 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 golpea antes de abandonar el código del repositorio.
Me pregunto si regresar 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 que llama a otro repositorio para construir uno aún más grande IQueryable
.
fuente
where
cláusula a un diferidoIQueryable
, solo tiene que enviar esos datos por cable, no todo el conjunto de resultados.Respuestas:
Devolver IQueryable definitivamente brindará 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, nuevamente, esto podría ser bueno o malo. Usted va (/ debería) es necesario definir los contratos de comportamiento que sus implementaciones se adhieren a, pero dónde va depende de usted.
Por lo tanto, puede colocar la lógica de recuperación arenosa fuera del repositorio y dejar que se use como quiera el usuario. Por lo tanto, exponer IQueryable brinda la mayor flexibilidad y permite consultas eficientes en lugar de filtrado en memoria, etc., y podría reducir la necesidad de hacer una tonelada de métodos de obtención de datos específicos.
Por otro lado, ahora le has dado a tus usuarios una escopeta. Pueden hacer cosas que quizás no haya planeado (usar en exceso .include (), realizar pesadas consultas pesadas y filtrar en la memoria en sus respectivas implementaciones, etc.), lo que básicamente evitaría los controles de comportamiento y estratificación porque ha dado Acceso completo.
Entonces, dependiendo del equipo, su experiencia, el tamaño de la aplicación, la estratificación general y la arquitectura ... depende: - \
fuente
Expression<Func<Foo,bool>>
método a su método. Estos parámetros se pueden pasarWhere
directamente al método, lo que permite al consumidor elegir su filtro sin necesidad de acceder directamente al IQueryable <Foo>.Exponer IQueryable a interfaces públicas no es una buena práctica. La razón es fundamental: no puede proporcionar la realización 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 hay una expectativa de implementación completa. IQueryable es un truco:
El patrón de repositorio nos brinda una representación clara por capas y, lo más importante, independencia de realización. Entonces podemos cambiar en la fuente de datos futura.
La pregunta es " ¿Debería haber posibilidad de sustitución de la fuente de datos? ".
Si mañana parte de los datos se pueden mover a los servicios de SAP, a la base de datos NoSQL, o simplemente a los archivos de texto, ¿podemos garantizar la implementación adecuada de la interfaz IQueryable?
( más puntos buenos acerca de por qué esta es una mala práctica)
fuente
Siendo realistas, tiene tres alternativas si desea una ejecución diferida:
IQueryable
.GetCustomersInAlaskaWithChildren
abajo)IQueryable
internamente.Prefiero el tercero (aunque también he ayudado a colegas a implementar el segundo), pero obviamente hay algo de configuración y mantenimiento involucrado. (T4 al rescate!)
Editar : para aclarar la confusión en torno a lo que estoy hablando, considere el siguiente ejemplo robado de IQueryable puede matar a su perro, robar a su esposa, matar su voluntad de vivir, etc.
En el escenario donde expondrías algo como esto:
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 IQueryable y le devolvería los resultados. 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 obtenga los resultados, aún puede LINQ-to-objects en él al contenido de su corazón.Otra ventaja de un enfoque como este es que, como son clases POCO, este repositorio puede vivir detrás de un servicio web o WCF; puede estar expuesto a AJAX u otras personas que no saben lo primero sobre LINQ.
fuente
IQueryable
sin exponerse aIQueryable
sí mismo . ¿Cómo vas a hacer eso con la API incorporada?Realmente solo hay una respuesta legítima: depende de cómo se use el repositorio.
En un extremo, su repositorio es un envoltorio muy delgado alrededor de un DBContext para que pueda inyectar una apariencia de capacidad de prueba alrededor de una aplicación basada en bases de datos. Realmente no existe una expectativa del mundo real de que esto se pueda usar de manera desconectada sin un DB compatible con LINQ detrás de él porque su aplicación CRUD nunca va a necesitar eso. Entonces claro, ¿por qué no usar IQueryable? Probablemente preferiría IEnumarable ya que obtienes la mayoría de los beneficios [léase: ejecución demorada] 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 de repositorio y devolver las colecciones materializadas apropiadas que no tienen una implicación de una conexión de base de datos subyacente.
fuente
IEnumerable
, es importante tener en cuenta que((IEnumerable<T>)query).LastOrDefault()
es muy diferente dequery.LastOrDefault()
(suponiendo quequery
es unIQueryable
). Por lo tanto, solo tiene sentido regresarIEnumerable
si vas a recorrer todos los elementos de todos modos.NO si desea realizar un desarrollo basado en pruebas / pruebas unitarias porque no hay una manera fácil de crear un repositorio simulado para probar su lógica de negocios (= repositorio-consumidor) aislada de la base de datos u otro proveedor IQueryable.
fuente
Desde un punto de vista de arquitectura pura, IQueryable es una abstracción con fugas. Como 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, ordenable, agrupable ... etc. De hecho, prefiero crear DTO en los que mis entidades se mapean y el IQueryable devuelve el DTO. De esa manera, prefiltro mis datos y realmente solo uso el método IQueryable para permitir la personalización de los resultados por parte del cliente.
fuente
Me he enfrentado a esa elección también.
Entonces, resumamos los lados positivo y negativo:
Positivos:
Negativos:
Recomendaría no usar este enfoque. Considere en su lugar crear varias Especificaciones e implementar métodos de repositorio, que puedan consultar la base de datos utilizando esos. El patrón de especificación es una excelente manera de encapsular un conjunto de condiciones.
fuente
Creo que exponer IQueryable en su repositorio es perfectamente aceptable durante las fases iniciales de desarrollo. Existen herramientas de interfaz de usuario que pueden funcionar con IQueryable directamente y manejar la clasificación, el filtrado, la agrupación, etc.
Dicho esto, creo que simplemente exponer la basura 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, exponer IQueryable públicamente en el repositorio permite un crecimiento más rápido. Antes de la primera versión completa, recomendaría hacer que la operación de consulta sea privada o protegida y que las consultas comodines estén bajo un mismo techo.
fuente
Lo que he visto hacer (y lo que estoy implementando) es lo siguiente:
Lo que esto le permite hacer es tener la flexibilidad de hacer una
IQueryable.Where(a => a.SomeFilter)
consulta en su repositorioconcreteRepo.GetAll(a => a.SomeFilter)
sin exponer ningún negocio LINQy.fuente