在表达式树中合并表达式

6

当表达式的部分作为参数传递时,我该如何构建表达式树?

例如,如果我想创建以下表达式树:

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

但是通过间接创建它们:
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));
}

结果:

虽然示例可能不太好理解(抱歉,我试图让它简单易懂),但这是结果(感谢Quartermeister)。

它可以与Linq-to-Sql一起使用,以搜索以findText开头或等于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);
3个回答

5
您可以使用Expression.Invoke创建表示将一个表达式应用于另一个表达式的表达式,并使用Expression.Lambda为组合表达式创建新的lambda表达式。类似这样:
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));
}

内部Expression.Invoke表示表达式,外部表示在select(x)返回的值上调用y => y.StartsWith(find)。您也可以使用Expression.Call来表示对StartsWith的调用,而无需使用第二个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));
}

谢谢,你的第一个回答正是我想要的! - laktak
这里需要注意的是,它可以与LINQ2SQL和LINQ2Entities一起使用,但不能与EF一起使用。由于某些原因,EF没有实现Expression.Invoke - nicodemus13

3

这个有效:

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

使用方法如下:

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

1
通常你不会用IQueryable接口描述的方式来做这件事,而是使用Expression,例如Expression < Func < TResult,T >>。话虽如此,您将高阶函数(如where或select)组合成查询,并传递表达式,这些表达式将“填充”所需的功能。
例如,考虑Enumerable.Where方法的签名:
Where<TSource>(IEnumerable<TSource>, Func<TSource, Boolean>)

该函数的第二个参数是一个委托,将在每个元素上调用。从该委托返回的值指示高阶函数是否应该产生当前元素(将其包含在结果中或不包含)。
现在,让我们来看看 Queryable.Where:
Queryable.Where<TSource>-Methode (IQueryable<TSource>, Expression<Func<TSource, Boolean>>)

我们可以观察到一个更高阶函数的相同模式,但是它不是使用 Func<> 委托,而是使用 Expression。表达式基本上是您代码的数据表示形式。编译该表达式将为您提供一个真正的(可执行的)委托。编译器通过从分配给 Expression<...> 的 lambda 构建表达式树来完成大量繁重的工作。表达式树使得可以针对不同的数据源(例如 SQL Server 数据库)编译所描述的代码。
回到您的示例,我认为您正在寻找的是一个选择器。选择器接受每个输入元素并返回其投影。它的签名如下:Expression<Func<TResult, T>>。例如,您可以指定以下内容:
Expression<Func<int, string>> numberFormatter = (i) => i.ToString(); // projects an int into a string

如果要传递选择器,则代码应该如下所示:

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

这个选择器可以让您将输入字符串投影到所需的类型。我希望我正确理解了您的意图,因为很难看出您想要实现什么。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接