2009-04-23 11 views
27

J'ai un tableau de chaînes de taille variable, et j'essaie de faire une boucle dans le tableau et de faire correspondre toutes les lignes d'une table où la colonne "Tags" contient au moins une des chaînes dans le tableau. Voici un code pseudo:Comment ajouter dynamiquement l'opérateur OU à la clause WHERE dans LINQ

IQueryable<Songs> allSongMatches = musicDb.Songs; // all rows in the table 

Je peux facilement interroger ce filtrage de table sur un ensemble fixe de chaînes, comme ceci:

allSongMatches=allSongMatches.Where(SongsVar => SongsVar.Tags.Contains("foo1") || SongsVar.Tags.Contains("foo2") || SongsVar.Tags.Contains("foo3")); 

Cependant, cela ne fonctionne pas (je reçois l'erreur suivante: « une expression lambda avec un corps de déclaration ne peut pas être converti en un arbre d'expression »)

allSongMatches = allSongMatches.Where(SongsVar => 
    { 
     bool retVal = false; 
     foreach(string str in strArray) 
     { 
     retVal = retVal || SongsVar.Tags.Contains(str); 
     } 
     return retVal; 
    }); 

Quelqu'un peut-il me montrer la bonne stratégie pour y parvenir? Je suis encore nouveau dans le monde de LINQ :-)

Répondre

32

Vous pouvez utiliser la classe PredicateBuilder:

var searchPredicate = PredicateBuilder.False<Songs>(); 

foreach(string str in strArray) 
{ 
    var closureVariable = str; // See the link below for the reason 
    searchPredicate = 
    searchPredicate.Or(SongsVar => SongsVar.Tags.Contains(closureVariable)); 
} 

var allSongMatches = db.Songs.Where(searchPredicate); 

LinqToSql strange behaviour

+0

Merci Mehrdad, votre solution est venue en un temps record et a travaillé comme un charme! Je n'étais pas au courant de la classe PredicateBuilder. Quelle fonctionnalité pratique! J'apprécie aussi votre commentaire sur l'utilisation d'une variable temporaire pour tenir les cordes ... qui m'aurait rendu fou! Victor –

+0

De rien! –

+0

J'ai obtenu Confus au sujet de l'expression False pour créer la var searchPredicate, Cela doit être faux? ou quoi que ce soit d'autre pourrait convenir? (Parler du PredicateBuilder.False ()) – Daniel

0

Soit construire une Expression<T> vous-même, ou de regarder un itinéraire différent.

En supposant que possibleTags est une collection de tags, vous pouvez utiliser une fermeture et une jointure pour trouver des correspondances. Cela devrait trouver des chansons avec au moins une étiquette dans possibleTags:

allSongMatches = allSongMatches.Where(s => (select t from s.Tags 
              join tt from possibleTags 
               on t == tt 
              select t).Count() > 0) 
+0

Hey Richard, j'apprécie vos commentaires . Pour une raison quelconque, VS n'a pas aimé la syntaxe du code que vous avez proposé ... est-ce que je manque quelque chose d'évident? –

+0

Très probablement a une erreur ... aurait besoin d'un peu de temps pour mettre ensemble harnais de données/test pour confirmer. Mais ont ajouté une clause select à l'expression de compréhension interne ... ce qui est requis (oups). – Richard

0

Il y a une autre méthode un peu plus facile qui y arriver. Le blog de ScottGu détaille une bibliothèque linq dynamique que j'ai trouvée très utile par le passé. Pour l'essentiel, il génère la requête à partir d'une chaîne que vous passez dans Voici un échantillon du code que vous écririez:.

Dim Northwind As New NorthwindDataContext 

Dim query = Northwind.Products _ 
        .Where("CategoryID=2 AND UnitPrice>3") _ 
        .OrderBy("SupplierId") 

Gridview1.DataSource = query 
Gridview1.DataBind() 

Plus d'informations sont disponibles sur le blog de ScottGu here.

+0

ne sais pas pourquoi ce -1, semble une réponse légitime – piris

1

J'ai récemment créé une méthode d'extension pour créer des recherches de chaînes qui permettent également des recherches OR. Blogué sur here

J'ai aussi créé comme un paquet NuGet que vous pouvez installer:

http://www.nuget.org/packages/NinjaNye.SearchExtensions/

Une fois installé, vous pourrez effectuer les opérations suivantes

var result = db.Songs.Search(s => s.Tags, strArray); 

Si vous voulez créer votre propre version pour permettre ce qui précède, vous devrez faire ce qui suit:

public static class QueryableExtensions 
{ 
    public static IQueryable<T> Search<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, params string[] searchTerms) 
    { 
     if (!searchTerms.Any()) 
     { 
      return source; 
     } 

     Expression orExpression = null; 
     foreach (var searchTerm in searchTerms) 
     { 
      //Create expression to represent x.[property].Contains(searchTerm) 
      var searchTermExpression = Expression.Constant(searchTerm); 
      var containsExpression = BuildContainsExpression(stringProperty, searchTermExpression); 

      orExpression = BuildOrExpression(orExpression, containsExpression); 
     } 

     var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters); 
     return source.Where(completeExpression); 
    } 

    private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd) 
    { 
     if (existingExpression == null) 
     { 
      return expressionToAdd; 
     } 

     //Build 'OR' expression for each property 
     return Expression.OrElse(existingExpression, expressionToAdd); 
    } 
} 

Sinon, jetez un coup d'oeil au projet github pour NinjaNye.SearchExtensions comme cela a d'autres options et a été refondus quelque peu pour permettre à d'autres combinaisons

Questions connexes