2010-08-10 3 views
6

Comment puis-je créer une arborescence d'expression lorsque des parties de l'expression sont passées en arguments?Combinaison d'expressions dans un arbre d'expression

E.g. si je voulais créer des arbres d'expression comme ceux-ci:

IQueryable<LxUser> test1(IQueryable<LxUser> query, string foo, string bar) 
{ 
    query=query.Where(x => x.Foo.StartsWith(foo)); 
    return query.Where(x => x.Bar.StartsWith(bar)); 
} 

mais en les créant indirectement:

IQueryable<LxUser> test2(IQueryable<LxUser> query, string foo, string bar) 
{ 
    query=testAdd(query, x => x.Foo, foo); 
    return testAdd(query, x => x.Bar, bar); 
} 

IQueryable<T> testAdd<T>(IQueryable<T> query, 
    Expression<Func<T, string>> select, string find) 
{ 
    // how can I combine the select expression with StartsWith? 
    return query.Where(x => select(x) .. y => y.StartsWith(find)); 
} 

Résultat:

Alors que les échantillons n'a pas beaucoup de sens (désolé mais j'essayais de rester simple), voici le résultat (merci Quartermeister).

Il peut être utilisé avec Linq-to-Sql pour rechercher une chaîne commençant par - ou égale à findText.

public static IQueryable<T> WhereLikeOrExact<T>(IQueryable<T> query, 
    Expression<Func<T, string>> selectField, string findText) 
{ 
    Expression<Func<string, bool>> find; 
    if (string.IsNullOrEmpty(findText) || findText=="*") return query; 

    if (findText.EndsWith("*")) 
    find=x => x.StartsWith(findText.Substring(0, findText.Length-1)); 
    else 
    find=x => x==findText; 

    var p=Expression.Parameter(typeof(T), null); 
    var xpr=Expression.Invoke(find, Expression.Invoke(selectField, p)); 

    return query.Where(Expression.Lambda<Func<T, bool>>(xpr, p)); 
} 

par exemple.

var query=context.User; 

query=WhereLikeOrExact(query, x => x.FirstName, find.FirstName); 
query=WhereLikeOrExact(query, x => x.LastName, find.LastName); 

Répondre

5

Vous pouvez utiliser Expression.Invoke pour créer une expression qui représente l'application d'une expression à l'autre, et Expression.Lambda pour créer un nouvelle expression lambda pour l'expression combinée. Quelque chose comme ceci:

IQueryable<T> testAdd<T>(IQueryable<T> query, 
    Expression<Func<T, string>> select, string find) 
{ 
    Expression<Func<string, bool>> startsWith = y => y.StartsWith(find); 
    var parameter = Expression.Parameter(typeof(T), null); 
    return query.Where(
     Expression.Lambda<Func<T, bool>>(
      Expression.Invoke(
       startsWith, 
       Expression.Invoke(select, parameter)), 
      parameter)); 
} 

La Expression.Invoke intérieure représente l'expression select(x) et l'enveloppe extérieure représente l'appel y => y.StartsWith(find) sur la valeur retournée par select(x).

Vous pouvez également utiliser Expression.Call pour représenter l'appel à StartsWith sans utiliser une seconde lambda:

IQueryable<T> testAdd<T>(IQueryable<T> query, 
    Expression<Func<T, string>> select, string find) 
{ 
    var parameter = Expression.Parameter(typeof(T), null); 
    return query.Where(
     Expression.Lambda<Func<T, bool>>(
      Expression.Call(
       Expression.Invoke(select, parameter), 
       "StartsWith", 
       null, 
       Expression.Constant(find)), 
      parameter)); 
} 
+0

Merci, votre première réponse était exactement ce que je cherchais! – laktak

+0

Une note importante ici est que cela fonctionnera avec LINQ2SQL et LINQ2Entities, mais non avec EF-EF, pour des raisons mieux connues, n'implémente pas 'Expression.Invoke'. – nicodemus13

3

This Works:

public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector1, 
        Expression<Func<T, string>> Selector2, string data1, string data2) 
{ 
    return Add(Add(query, Selector1, data1), Selector2, data2); 
} 

public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector, string data) 
{ 
    var row = Expression.Parameter(typeof(T), "row"); 
    var expression = 
     Expression.Call(
      Expression.Invoke(Selector, row), 
      "StartsWith", null, Expression.Constant(data, typeof(string)) 
     ); 
    var lambda = Expression.Lambda<Func<T, bool>>(expression, row); 
    return query.Where(lambda); 
} 

Vous l'utilisez comme:

IQueryable<XlUser> query = SomehowInitializeIt(); 
query = Add(query, x => x.Foo, y => y.Bar, "Foo", "Bar"); 
1

En général, vous ne le faites pas dans la façon dont vous descirbed (en utilisant l'interface IQueryable) mais vous utilisez plutôt Expressions comme Expression<Func<TResult, T>>. Cela dit, vous composez des fonctions d'ordre supérieur (telles que where ou select) dans une requête et transmettez des expressions qui "rempliront" la fonctionnalité désirée.

Par exemple, considérons la signature de la méthode Enumerable.Where:

Where<TSource>(IEnumerable<TSource>, Func<TSource, Boolean>) 

La fonction prend un délégué comme second argument qui est appelé sur chaque élément. La valeur que vous renvoyez de ce délégué indique à la fonction d'ordre supérieur si elle doit donner l'élément en cours (l'inclure dans le résultat ou non).

Maintenant, nous allons jeter un oeil à Queryable.Where:

Queryable.Where<TSource>-Methode (IQueryable<TSource>, Expression<Func<TSource, Boolean>>) 

On peut observer le même schéma d'une fonction d'ordre supérieur, mais au lieu d'un délégué Func<> il prend une expression. Une expression est essentiellement une représentation de données de votre code.Compiler cette expression vous donnera un vrai délégué (exécutable). Le compilateur fait beaucoup de travail pour construire des arbres d'expression à partir de lambdas que vous attribuez à Expression<...>. Les arborescences d'expression permettent de compiler le code décrit par rapport à différentes sources de données, telles qu'une base de données SQL Server.

Pour revenir à votre exemple, ce que je pense que vous cherchez est un sélecteur. Un sélecteur prend chaque élément d'entrée et renvoie une projection de celui-ci. Sa signature ressemble à ceci: Expression<Func<TResult, T>>. Par exemple, vous pouvez spécifier celui-ci:

Expression<Func<int, string>> numberFormatter = (i) => i.ToString(); // projects an int into a string 

Pour passer dans un sélecteur, votre code devrait ressembler à ceci:

IQueryable<T> testAdd<T>(IQueryable<T> query, Expression<Func<string, T>> selector, string find) 
{ 
    // how can I combine the select expression with StartsWith? 
    return query.Select(selector) // IQueryable<string> now 
       .Where(x => x.StartsWith(find)); 
} 

Ce sélecteur vous permettra de projeter la chaîne d'entrée au choix type. J'espère que votre intention a été corrigée, il est difficile de voir ce que vous essayez d'accomplir.