2010-10-28 5 views
4

Disons que j'ai un enregistrement Personne dans une base de données, et qu'il y a un champ Âge pour la personne.LINQ comment interroger si une valeur est entre une liste de plages?

Maintenant, j'ai une page qui me permet de filtrer pour les personnes dans certaines tranches d'âge. Par exemple, je peux choisir plusieurs sélections de gamme, telles que «0-10», «11-20», «31-40».

Donc dans ce cas, je récupérerais une liste de personnes entre 0 et 20, ainsi que de 30 à 40, mais pas de 21 à 30.

J'ai pris les tranches d'âge et peuplé d'une liste des gammes qui ressemble à ceci:

class AgeRange 
{ 
    int Min { get; set; } 
    int Max { get; set; } 
} 

List<AgeRange> ageRanges = GetAgeRanges(); 

J'utilise LINQ to SQL pour mon accès de base de données et les requêtes, mais je ne peux pas comprendre comment interroger les plages.

Je veux faire quelque chose comme ça, mais bien sûr, cela ne fonctionnera pas car je ne peux pas interroger mes valeurs locales contre les valeurs SQL:

var query = from person in db.People 
      where ageRanges.Where(ages => person.Age >= ages.Min && person.Age <= ages.Max).Any()) 
      select person; 
+0

Quelle erreur exacte recevez-vous? –

+0

NotSupportedException - La séquence locale ne peut pas être utilisée dans les implémentations LINQ to SQL des opérateurs de requête à l'exception de l'opérateur Contient. – Makotosan

+0

Pourquoi ne pouvez-vous pas interroger vos valeurs locales par rapport aux valeurs db? Ce que je pense que vous devrez faire ici est de faire la requête dans une boucle sur les ensembles de AgeRanges et d'Union les requêtes en utilisant Linq. Je vais devoir voir si je peux trouver un exemple comme je ne l'ai jamais fait auparavant ... (le syndicat que je veux dire) – EJC

Répondre

10

Vous pouvez construire le prédicat dynamique avec PredicateBuilder :

static Expression<Func<Person, bool>> BuildAgePredicate(IEnumerable<AgeRange> ranges) 
{ 
    var predicate = PredicateBuilder.False<Person>(); 
    foreach (var r in ranges) 
    { 
     // To avoid capturing the loop variable 
     var r2 = r; 
     predicate = predicate.Or (p => p.Age >= r2.Min && p.Age <= r2.Max); 
    } 
    return predicate; 
} 

Vous pouvez ensuite utiliser cette méthode comme suit:

var agePredicate = BuildAgePredicate(ageRanges); 
var query = db.People.Where(agePredicate); 
+0

Oh, bien. Beaucoup plus propre que ce à quoi je pensais :) – EJC

+0

Quand je cours cela, il semble que seuls les gens dont l'âge est dans la dernière période spécifiée, mais pas d'autres. – diceguyd30

+0

+1 pour la propreté bien! Je vais certainement commencer à l'utiliser! – diceguyd30

0

Comme l'une de vos erreurs mentionnée, vous ne pouvez utiliser qu'une séquence locale avec la méthode 'Contient'. Une option serait alors de créer une liste de tous les âges autorisés comme ceci:

var ages = ageRanges 
     .Aggregate(new List<int>() as IEnumerable<int>, (acc, x) => 
      acc.Union(Enumerable.Range(x.Min,x.Max - (x.Min - 1))) 
     ); 

vous pouvez appeler:

People.Where(x => ages.Contains(x.Age)) 

Un mot d'avertissement à cette histoire, si vos gammes être grand, alors cela va échouer!

(Cela fonctionne bien pour les petites gammes (votre nombre maximum d'âges acceptés ne sera probablement jamais dépasser 100), mais plus que cela et les deux commandes ci-dessus deviendra très cher!)

+0

Le problème avec cette solution est qu'elle ne fonctionnera pas bien pour les grandes gammes. Par exemple, disons que je filtrais par tranches de salaires plutôt que par âges. – Makotosan

+0

En effet. Je me suis senti à l'aise avec ceci ici puisque la gamme sera relativement petite (100 éléments supérieurs?), Mais vous avez absolument raison. Tout autre domaine et cela pourrait être assez l'opération coûteuse! Je vais modifier avec un avertissement. – diceguyd30

0

Merci à La réponse de Thomas, j'ai été capable de créer cette version plus générique qui semble fonctionner:

static IQueryable<T> Between<T>(this IQueryable<T> query, Expression<Func<T, decimal>> predicate, IEnumerable<NumberRange> ranges) 
    { 
     var exp = PredicateBuilder.False<T>(); 

     foreach (var range in ranges) 
     { 
      exp = exp.Or(
        Expression.Lambda<Func<T, bool>>(Expression.GreaterThanOrEqual(predicate.Body, Expression.Constant(range.Min)), predicate.Parameters)) 
        .And(Expression.Lambda<Func<T, bool>>(Expression.LessThanOrEqual(predicate.Body, Expression.Constant(range.Max)), predicate.Parameters)); 
     } 

     return query.Where(exp); 
    } 
Questions connexes