组合表达式树中的表达式

发布于 2024-09-13 22:13:04 字数 1767 浏览 3 评论 0 原文

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

例如,如果我想创建像这样的表达式树:

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

例如

var query=context.User;

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

How can I build an expression tree when parts of the expression are passed as arguments?

E.g. what if I wanted to create expression trees like these:

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

but by creating them indirectly:

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

Result:

While the samples didn't make much sense (sorry but I was trying to keep it simple), here's the result (thanks Quartermeister).

It can be used with Linq-to-Sql to search for a string that starts-with or is equal to the 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);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

凉城已无爱 2024-09-20 22:13:04

您可以使用 Expression.Invoke 创建一个表示应用一个表达式的表达式到另一个,并 Expression.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)select(x) 返回的值进行处理。

您还可以使用 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));
}

You can use Expression.Invoke to create an expression that represents applying one expression to another, and Expression.Lambda to create a new lambda expression for the combined expression. Something like this:

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

The inner Expression.Invoke represents the expression select(x) and the outer one represents calling y => y.StartsWith(find) on the value returned by select(x).

You could also use Expression.Call to represent the call to StartsWith without using a second 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));
}
少女的英雄梦 2024-09-20 22:13:04

这有效:

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

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

You use it like:

IQueryable<XlUser> query = SomehowInitializeIt();
query = Add(query, x => x.Foo, y => y.Bar, "Foo", "Bar");
安稳善良 2024-09-20 22:13:04

通常,您不会按照您所描述的方式(使用 IQueryable 接口)执行此操作,而是使用诸如 Expression> 之类的表达式。话虽如此,您可以将高阶函数(例如 whereselect)组合到查询中,并传入将“填充”所需功能的表达式。

例如,考虑 Enumerable.Where 方法的签名:

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

该函数采用委托作为在每个元素上调用的第二个参数。从该委托返回的值向高阶函数指示它是否会产生当前元素(是否将其包含在结果中)。

现在,让我们看一下 Queryable.Where :

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

我们可以观察高阶函数的相同模式,但它需要一个表达式而不是 Func<> 委托。表达式基本上是代码的数据表示。编译该表达式将为您提供一个真正的(可执行的)委托。编译器执行大量繁重工作来根据分配给 Expression<...> 的 lambda 构建表达式树。表达式树使得可以针对不同的数据源(例如 SQL Server 数据库)编译所描述的代码。

回到你的例子,我认为你正在寻找的是一个选择器。选择器获取每个输入元素并返回它的投影。它的签名如下所示:Expression>。例如,您可以指定这个:

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

该选择器将允许您将输入字符串投影为所需的类型。我希望我正确地理解了你的意图,很难看出你想要实现什么。

Usually you don't do that in the way you descirbed (using the IQueryable Interface) but you rather use Expressions like Expression<Func<TResult, T>>. Having said that, you compose higher order functions (such as where or select) into a query and pass in expressions that will "fill in" the desired functionality.

For example, consider the signature of the Enumerable.Where method:

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

The function takes a delegate as its second argument that is called on each element. The value you return from that delegate indicates to the higher order function if it shall yield the current element (include it in the result or not).

Now, let's take a look at Queryable.Where:

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

We can observe the same pattern of a higher order function, but instead of an Func<> delegate it takes an Expression. An expression is basically a data representation of your code. Compiling that expression will give you a real (executable) delegate. The compiler does a lot of heavy lifting to build expression trees from lambdas you assign to Expression<...>. Expression trees make it possible to compile the described code against different data sources, such as a SQL Server Database.

To come back to your example, what I think you're looking for is a selector. A selector takes each input element and returns a projection of it. It's signature looks like this: Expression<Func<TResult, T>>. For example you could specify this one:

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

To pass in a selector, your code would need to look like this:

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

This selector would allow you to project the input string to the desired type. I hope I got your intention corrrectly, it's hard to see what you're trying to achieve.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文