5

Durante la construcción por DAL Repository, me encontré con un concepto llamado Pipes and Filters. Leí al respecto here, aquí y vi un screencast de here. Todavía no estoy seguro de cómo implementar este patrón. Teóricamente, todo suena bien, pero ¿cómo implementamos esto en un escenario empresarial?¿Cómo se implementa el patrón de Tuberías y filtros con LinqToSQL/Entity Framework/NHibernate?

Agradeceré, si tiene algún recurso, consejos o ejemplos para explicar este patrón en contexto a los correlacionadores de datos/ORM mencionados en la pregunta.

Gracias de antemano!

+0

Agregue un comentario si decide desestimar la pregunta por cualquier motivo. – Perpetualcoder

+0

¿Algo está mal con la pregunta? – Perpetualcoder

Respuesta

11

En última instancia, LINQ en IEnumerable<T>es una implementación de tuberías y filtros. IEnumerable<T> es una transmisión de API, lo que significa que los datos se devuelven sin problemas a medida que lo solicite (a través de bloques de iteradores), en lugar de cargar todo a la vez y devolver un gran búfer de registros.

Esto significa que su consulta:

var qry = from row in source // IEnumerable<T> 
      where row.Foo == "abc" 
      select new {row.ID, row.Name}; 

es:

var qry = source.Where(row => row.Foo == "abc") 
      .Select(row = > new {row.ID, row.Name}); 

como enumerar por esto, que consumirá los datos con pereza. Puede ver esto gráficamente con Jon Skeet's Visual LINQ. Las únicas cosas que rompen la tubería son las cosas que fuerzan el almacenamiento en búfer; OrderBy, GroupBy, etc. Para trabajos de gran volumen, Jon y yo trabajamos en Push LINQ para hacer agregados sin almacenamiento en esos escenarios.

IQueryable<T> (expuesto por la mayoría de las herramientas ORM - LINQ-to-SQL, Entity Framework, LINQ-a-NHibernate) es una bestia ligeramente diferente; porque el motor de la base de datos va a hacer la mayor parte del trabajo pesado, es probable que la mayoría de los pasos ya se hayan realizado; todo lo que queda es consumir un IDataReader y proyectarlo a objetos/valores, pero eso sigue siendo una tubería (IQueryable<T> implementa IEnumerable<T>) a menos que llame .ToArray(), etc. .ToList()

con respecto a su uso en la empresa ... my view es que es muy bien utilizar IQueryable<T> escribir consultas componibles dentro del repositorio, pero no deben licencia el repositorio, ya que eso haría que el funcionamiento interno del repositorio esté sujeto a la persona que llama, por lo que no podría probar/un perfil/optimizar/etc. correctamente. He llevado a hacer cl Alguna vez cosas en el repositorio, pero regresan listas/matrices. Esto también significa que mi repositorio no se da cuenta de la implementación.

Esto es una lástima, ya que la tentación de "devolver" IQueryable<T> desde un método de repositorio es bastante grande; por ejemplo, esto le permitiría a la persona que llama agregar paginación/filtros/etc., pero recuerde que aún no han consumido los datos. Esto hace que la administración de recursos sea un dolor. Además, en MVC, etc., deberá asegurarse de que el controlador llame al .ToList() o similar, de modo que no sea la vista la que controle el acceso a los datos (de lo contrario, tampoco podrá probar el controlador de manera adecuada).

Un uso seguro (OMI) de los filtros en el DAL sería cosas como:

public Customer[] List(string name, string countryCode) { 
    using(var ctx = new CustomerDataContext()) { 
     IQueryable<Customer> qry = ctx.Customers.Where(x=>x.IsOpen); 
     if(!string.IsNullOrEmpty(name)) { 
      qry = qry.Where(cust => cust.Name.Contains(name)); 
     } 
     if(!string.IsNullOrEmpty(countryCode)) { 
      qry = qry.Where(cust => cust.CountryCode == countryCode); 
     } 
     return qry.ToArray(); 
    } 
} 

filtros Aquí hemos añadido el la marcha, pero no pasa nada hasta que nosotros llamamos ToArray. En este punto, los datos se obtienen y se devuelven (eliminando el contexto de datos en el proceso). Esto puede ser probado por completo. Si hemos hecho algo similar, pero acabamos de regresar IQueryable<T>, la persona que llama podría hacer algo como:

var custs = customerRepository.GetCustomers() 
     .Where(x=>SomeUnmappedFunction(x)); 

Y, de repente, nuestra DAL comienza a fallar (no se puede traducir a SomeUnmappedFunction TSQL, etc). Sin embargo, aún puede hacer un lote de cosas interesantes en el repositorio.

El único punto de dolor aquí es que puede empujarlo a tener algunas sobrecargas para admitir diferentes patrones de llamadas (con/sin paginación, etc.). Hasta que llegue el optional/named parameters, la mejor respuesta aquí es usar métodos de extensión en la interfaz; de esa manera, sólo necesito una implementación concreta del repositorio:

class CustomerRepository { 
    public Customer[] List(
     string name, string countryCode, 
     int? pageSize, int? pageNumber) {...} 
} 
interface ICustomerRepository { 
    Customer[] List(
     string name, string countryCode, 
     int? pageSize, int? pageNumber); 
} 
static class CustomerRepositoryExtensions { 
    public static Customer[] List(
      this ICustomerRepository repo, 
      string name, string countryCode) { 
     return repo.List(name, countryCode, null, null); 
    } 
} 

Ahora tenemos sobrecargas virtuales (como los métodos de extensión) en ICustomerRepository - por lo que nuestro interlocutor puede utilizar repo.List("abc","def") sin tener que especificar la localización.


Finalmente, sin LINQ, el uso de tuberías y filtros se vuelve mucho más doloroso. Escribirás algún tipo de consulta basada en texto (TSQL, ESQL, HQL). Obviamente puedes agregar cadenas, pero no es muy "pipe/filter" -ish. La "API de criterios" es un poco mejor, pero no tan elegante como LINQ.

Cuestiones relacionadas