2010-08-10 9 views
6

¿Cómo puedo construir un árbol de expresiones cuando partes de la expresión se pasan como argumentos?Combinación de expresiones en un árbol de expresiones

E.g. ¿y si yo quería crear árboles de expresión como éstas:

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)); 
} 

sino creando indirectamente:

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)); 
} 

Resultado:

Mientras que las muestras no tenía mucho sentido (lo siento pero estaba tratando de mantenerlo simple), aquí está el resultado (gracias Quartermeister).

Se puede usar con Linq-to-Sql para buscar una cadena que comience con o sea igual a 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)); 
} 

e.g.

var query=context.User; 

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

Respuesta

5

Puede utilizar Expression.Invoke para crear una expresión que representa la aplicación de una expresión a otra, y Expression.Lambda para crear una nueva expresión lambda para la expresión combinada. Algo como esto:

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)); 
} 

El Expression.Invoke interior representa la expresión select(x) y la exterior representa llamando y => y.StartsWith(find) en el valor devuelto por select(x).

También es posible usar Expression.Call para representar la llamada a StartsWith sin necesidad de utilizar un segundo 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

¡Gracias, su primera respuesta fue exactamente lo que estaba buscando! – laktak

+0

Una nota importante aquí es que funcionará con LINQ2SQL y LINQ2Entities, pero no con EF-EF, por razones que son conocidas por sí mismas, no implementa 'Expression.Invoke'. – nicodemus13

3

Esto funciona:

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); 
} 

Se utiliza como:

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

Por lo general, no lo hace en la forma en que descirbed (utilizando la interfaz IQueryable), sino que más bien utilizar Expresiones como Expression<Func<TResult, T>>. Una vez dicho esto, compones funciones de orden superior (como where o select) en una consulta y pasas expresiones que "completarán" la funcionalidad deseada.

Por ejemplo, considere la firma del método Enumerable.Where:

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

La función toma un delegado como segundo argumento que se llama en cada elemento. El valor que devuelve de ese delegado indica a la función de orden superior si debe ceder el elemento actual (incluirlo en el resultado o no).

Ahora, vamos a echar un vistazo a Queryable.Where:

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

podemos observar el mismo patrón de una función de orden superior, pero en lugar de un delegado Func<> que toma una expresión. Una expresión es básicamente una representación de datos de tu código.Compilar esa expresión le dará un delegado real (ejecutable). El compilador hace un montón de trabajo pesado para construir árboles de expresión de lambdas que asigne al Expression<...>. Los árboles de expresiones permiten compilar el código descrito con diferentes orígenes de datos, como una base de datos de SQL Server.

volver a su ejemplo, lo que creo que estás buscando es un selector de . Un selector toma cada elemento de entrada y devuelve una proyección de él. Su firma se ve así: Expression<Func<TResult, T>>. Por ejemplo, podría especificar la siguiente:

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

Para pasar de un selector, el código debería tener este aspecto:

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)); 
} 

Este selector le permitirá proyectar la cadena de entrada a la deseada tipo. Espero que tenga su intención correcta, es difícil ver lo que está tratando de lograr.

Cuestiones relacionadas