2009-12-04 2 views
2

ok les gars, nues avec moi. Je résumerai d'abord, puis entrerai dans les détails.Combinaison de plusieurs expressions (Expression <Func<T,bool>>) ne fonctionnant pas avec des variables. Pourquoi?

J'ai écrit un certain nombre de méthodes (.WhereOr, .WhereAnd) qui me permettent fondamentalement d'empiler un tas de requêtes lambda, puis de les appliquer à une collection. Par exemple, l'utilisation des ensembles de données serait un peu comme ça (même si cela fonctionne avec une classe en utilisant les génériques):

AVEC LINQ À DATASETS (À l'aide des DataSetExtensions .NET)

DataTable Result; 

List<Expression<Func<DataRow, bool>> Queries = new List<Expression<Func<DataRow, bool>>(); 

Queries.Add(dr=> dr.Field<string>("field1") == "somestring"); 
Queries.Add(dr=> dr.Field<string>("field2") == "somestring"); 
Queries.Add(dr=> dr.Field<string>("field3") == "somestring"); 

Result = GetSomeTable().AsEnumarable().WhereOr(Queries).CopyToDataTable(); 

maintenant dire que dans l'exemple ci-dessus, une seule ligne de la collection correspond à "somestring", et c'est sur le champ "field2".

Cela signifie que le compte pour résultat devrait être 1.

Maintenant, je dis ré-écrire le code ci-dessus un peu à ceci:

DataTable Result; 

List<Expression<Func<DataRow, bool>> Queries = new List<Expression<Func<DataRow, bool>>(); 

List<string> columns = new string[]{"field1","field2","field3"}.ToList(); 

string col; 

foreach(string c in columns){ 
    col = c; 
    Queries.Add(dr=> dr.Field<string>(col) == "somestring"); 
} 

Result = GetSomeTable().AsEnumarable().WhereOr(Queries).CopyToDataTable(); 

Maintenant, je ne comprends pas vraiment expressions, mais pour moi les deux exemples ci-dessus font exactement la même chose.

Sauf que « Résultat » dans le premier exemple a un compte de 1 et « Résultat » dans le second exemple a un compte de 0.

En outre, dans la liste des colonnes dans le second exemple, si vous mettre "field2" en dernier, au lieu de second, alors "Result" a correctement le compte de 1.

Donc, de tout ceci je suis arrivé à une sorte de conclusion, mais je ne comprends pas vraiment ce qui se passe , ni comment le réparer ..? Puis-je "évaluer" ces expressions plus tôt ... ou en faire partie?

CONCLUSION:

En fait, il semble que, si j'envoie des valeurs littérales en là, comme "field1", cela fonctionne. Mais si j'envoie des variables, comme "col", cela ne fonctionne pas, car ces "expressions" ne sont évaluées que plus tard dans le code.

cela expliquerait aussi pourquoi cela fonctionne quand je déplace "field2" à la dernière position. cela fonctionne parce que la variable "col" a été affectée à "field2" enfin, donc au moment où les expressions évaluent "col" est égal à "field2".

Ok, donc, y a-t-il un moyen de contourner cela?

Voici le code pour ma méthode de WhereOr (il est une méthode d'extension pour IEnumerable):

public static IQueryable<T> WhereOr<T>(this IEnumerable<T> Source, List<Expression<Func<T, bool>>> Predicates) { 

     Expression<Func<T, bool>> FinalQuery; 

     FinalQuery = e => false; 

     foreach (Expression<Func<T, bool>> Predicate in Predicates) { 
      FinalQuery = FinalQuery.Or(Predicate); 
     } 

     return Source.AsQueryable<T>().Where(FinalQuery); 
    } 

public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> Source, Expression<Func<T, bool>> Predicate) { 
     InvocationExpression invokedExpression = Expression.Invoke(Predicate, Source.Parameters.Cast<Expression>()); 
     return Expression.Lambda<Func<T, bool>>(Expression.Or(Source.Body, invokedExpression), Source.Parameters); 
    } 
+0

Essayez de prendre l'expression finale que vous avez construit dans chaque sens et de faire « ToString » à elle. Vous obtiendrez une version joliment imprimée de l'expression que je trouve souvent utile lors de la construction dynamique des arbres d'expression. Vous devriez également google LinqKit, une bibliothèque qui fournit beaucoup de méthodes d'aide pour manipuler les arbres d'expression. –

Répondre

6

La réponse "comment résoudre". Changer ceci:

string col; 
foreach(string c in columns) { 
    col = c; 
    Queries.Add(dr=> dr.Field<string>(col) == "somestring"); 
} 

à ceci:

foreach(string c in columns) { 
    string col = c; 
    Queries.Add(dr=> dr.Field<string>(col) == "somestring"); 
} 

Profitez. La réponse "what & why" a été donnée par Brian.

+0

mec, vous êtes l'homme! Je vous remercie – andy

6
+1

(Je vais probablement me tromper cette fois en sautant la question, donc downvote-moi si je suis!) – Brian

+2

Nah, c'est le truc habituel, mais avec une torsion (il ne ferme pas une variable de boucle, il assigne une variable _explicitement_ déclaré _outside_ la boucle, puis se refermant sur celle-ci). –

+0

Pavel! Je ne comprends pas ce que vous venez de dire, mais vous êtes sur l'argent. mec, qu'est-ce que je fais mal? éduque moi. bravo l'homme – andy

1

Oh, après avoir commenté j'ai vu le problème.Vous utilisez la même variable, "col", dans chaque itération de la boucle. Lorsque vous générez l'expression lambda, elle ne se lie pas à la valeur de la variable, elle fait référence à la variable elle-même. Au moment où vous exécutez la requête, "Col" est défini sur tout ce qui est la dernière valeur. Essayez de créer une chaîne temporaire dans la boucle, en définissant sa valeur et en l'utilisant.

+0

exactement chris! c'est exactement ce qui se passe. Que voulez-vous dire en définissant un temp temp? Et, est-il un moyen de "forcer" une liaison des valeurs? Bravo – andy

+1

Non, la liaison est toujours sur les variables. Mais si vous déclarez une variable à l'intérieur de la boucle, alors vous obtiendrez une nouvelle variable pour chaque itération de la boucle, et donc chaque lambda se liera au sien (qui ne changera pas par la suite, sauf si le lambda décide de le changer). –

Questions connexes