5

En construisant par DAL Repository, je suis tombé sur un concept appelé Pipes and Filters. J'ai lu à ce sujet here, ici et j'ai vu un screencast de here. Je ne suis toujours pas sûr de savoir comment mettre en œuvre ce modèle. Théoriquement tout semble bien, mais comment pouvons-nous vraiment mettre en œuvre cela dans un scénario d'entreprise?Comment implémentez-vous le modèle Pipes and Filters avec LinqToSQL/Entity Framework/NHibernate?

J'apprécierai, si vous avez des ressources, des conseils ou des exemples d'explication de ce modèle dans le contexte des données mappers/ORM mentionnés dans la question.

Merci d'avance !!

+0

Veuillez ajouter un commentaire si vous décidez de reporter la question pour une raison quelconque. – Perpetualcoder

+0

Quelque chose ne va pas avec la question ?? – Perpetualcoder

Répondre

11

Finalement, LINQ sur IEnumerable<T>est une implémentation de tuyaux et de filtres. IEnumerable<T> est un streaming API - ce qui signifie que les données sont retournées paresseusement que vous le demandez (via des blocs d'itérateur), plutôt que de charger tout en même temps, et de retourner un grand tampon d'enregistrements.

Cela signifie que votre requête:

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

est:

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

que vous énumérez sur ce, il consommera les données paresseusement. Vous pouvez le voir graphiquement avec Visual LINQ de Jon Skeet. Les seules choses qui cassent le tuyau sont des choses qui forcent le tampon; OrderBy, GroupBy, etc. Pour un travail à volume élevé, Jon et moi-même avons travaillé sur Push LINQ pour effectuer des agrégats sans mise en mémoire tampon dans de tels scénarios.

IQueryable<T> (exposée par la plupart des outils ORM - LINQ-to-SQL, Entity Framework, LINQ-à-NHibernate) est une bête légèrement différente; Parce que le moteur de base de données va faire le gros du travail, les chances sont que la plupart des étapes sont déjà faites - il ne reste plus qu'à consommer un IDataReader et projeter ceci aux objets/valeurs - mais c'est toujours typiquement un tuyau (IQueryable<T> implémente IEnumerable<T>) à moins que vous appelez .ToArray(), .ToList() etc.

en ce qui concerne l'utilisation en entreprise ... my view est qu'il est bon d'utiliser IQueryable<T> pour écrire des requêtes composables à l'intérieur du dépôt, mais ils ne doivent pas congé le référentiel - car cela rendrait le fonctionnement interne du référentiel soumis à l'appelant, de sorte que vous seriez incapable de tester correctement unit/profile/optimize/etc. jamais dans le dépôt, mais retourne des listes/tableaux. Cela signifie également que mon référentiel reste inconscient de l'implémentation.

Ceci est une honte - comme la tentation de "retourner" IQueryable<T> d'une méthode de dépôt est assez grande; par exemple, cela permettrait à l'appelant d'ajouter paging/filters/etc - mais n'oubliez pas qu'il n'a pas encore consommé les données. Cela fait mal à la gestion des ressources. De même, dans MVC etc., vous devez vous assurer que le contrôleur appelle .ToList() ou similaire, afin que ce ne soit pas la vue qui contrôle l'accès aux données (sinon, vous ne pouvez pas non plus tester le contrôleur correctement).

Une utilisation sûre (OMI) des filtres dans le DAL serait des choses comme:

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

filtres Ici, nous avons ajouté à la volée, mais rien ne se passe jusqu'à ce que nous appelons ToArray. À ce stade, les données sont obtenues et renvoyées (en disposant le contexte de données dans le processus). Cela peut être entièrement testé. Si nous avons fait quelque chose de similaire, mais revenons tout juste IQueryable<T>, l'appelant peut faire quelque chose comme:

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

Et tout d'un coup notre DAL commence à ne pas (ne peut pas traduire SomeUnmappedFunction à TSQL, etc.). Vous pouvez toujours faire un lot des choses intéressantes dans le dépôt, cependant. Le seul point douloureux ici est qu'il pourrait vous pousser à avoir quelques surcharges pour supporter différents types d'appels (avec/sans pagination, etc.). Jusqu'à ce que optional/named parameters arrive, je trouve la meilleure réponse ici est d'utiliser des méthodes d'extension sur l'interface; De cette façon, je ne ai besoin d'une mise en œuvre du référentiel concret:

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

Maintenant, nous avons virtuel des surcharges (comme les méthodes d'extension) sur ICustomerRepository - donc notre appelant peut utiliser repo.List("abc","def") sans avoir à spécifier la pagination.


Enfin - sans LINQ, en utilisant les tuyaux et les filtres devient beaucoup plus douloureux. Vous allez écrire une sorte de requête textuelle (TSQL, ESQL, HQL). Vous pouvez évidemment ajouter des chaînes, mais ce n'est pas très "pipe/filter" -ish. L'API "Criteria" est un peu mieux - mais pas aussi élégante que LINQ.