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.
Veuillez ajouter un commentaire si vous décidez de reporter la question pour une raison quelconque. – Perpetualcoder
Quelque chose ne va pas avec la question ?? – Perpetualcoder