使用LINQ to SQL的表达式树连接 [英] Expression Tree Concatenation with LINQ to SQL

查看:70
本文介绍了使用LINQ to SQL的表达式树连接的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在努力使一个灵活的导出器从通过LINQ访问的SQL数据库中导出信息到SQL,从而程序可以动态选择要选择的字段并完成所有处理服务器端.

I'm having a go at making a flexible exporter to export info from a SQL db accessed via LINQ to SQL, whereby the program can dynamically choose which fields to select and do all the processing server side.

最终目的是要有一个简单的声明,例如:

The final aim is to have a simple statement like:

var result = db.Product.Select(p => selector(p));

选择器是动态创建的表达式树,描述了要选择的字段.目前,我已经将每个db字段分配给了自己的单独选择器,例如:

Where selector is a dynamically created Expression Tree describing the fields to select. Currently, I have each of the db fields assigned to it's own individual selector like:

Expression<Func<Product, string>> name = p => p.Name; 
Expression<Func<Product, string>> createdAt = p => p.createdAt.ToString();

此刻,我正在选择字段,并尝试从中创建一个连接表达式,该表达式从select语句返回逗号分隔的字符串结果,例如:

At the moment, I'm taking the chosen fields and trying to make one concatenated expression out of them that returns a comma delimited string result from the select statement, something like:

Expression<
  Func<
   Func<Product, string>,
   Func<Product, string>,
   Func<Product, string>>> combinator = (a, b) => (p) => a(p) + "," + b(p);

// So I can do something like...
Expression<Func<Product, string>> aggregate = p => "";

foreach (var field in fieldsToSelect)
    aggregate = combinator(aggregate, field);

// This would create...
Expression<Func<Products, string>> aggregate = p => a(p) + "," + b(p);

一旦我建立了包含许多字段的选择器,就可以在语句中执行它,并且所有处理都在服务器上完成.但是,我一直无法正确创建一个表达式来连接两个子函数,以致结果不是一个在往返之后简单执行以获取结果的Func:

Once I've built up my selector with however many fields, I can execute it in the statement and all the processing is done on the server. However, I've been unable to properly create an expression to concatenate the two child functions in such a manner that the result isn't a Func that's simply executed after the round trip to fetch the results:

var joinedSelector = combinator.Compile();
Func<Products, string> result = joinedSelector(firstSelector.Compile(), secondSelector.Compile());
var query  = db.Product.Select(p => result(p)).ToList();

从我对表达式树的有限理解来看,由于语句被编译为普通的Funcs,因此实际上并不会导致这种情况.我已经看过Expression.Coalesce(),但不确定是否是我要的(想想它只是做"??").

From my limited understanding of Expression Trees, this doesn't actually result in one as the statements are compiled to normal Funcs. I've looked at Expression.Coalesce() but am not sure if it's what I'm after (think it just does "??").

我对这种东西还很陌生,希望能对您有所帮助.

I'm pretty new to this sort of stuff, and would appreciate any help.

即使人们可以想出更好的方法来解决原始问题,我还是很想解释一下如何实现我要尝试做的事情,只是为了学习如何使用表达式树.

Even if people can think of better ways to attack the original problem, I'd still quite like an explanation of how to achieve what I'm trying to do just for the sake of learning how to use Expression Trees.

推荐答案

因此,您正在寻找创建一个可以组合两个选择器结果的Combine方法.为此,我们需要一个方法,该方法接受具有相同输入和相同输出的两个函数,然后是一个接受该输出类型的两个实例并返回新值的函数.

So you're looking to create a Combine method that can combine the results of two selectors. To do this we'll need a method that accepts two functions with the same input and same output, and then a function accepting two instances of that output type and returning a new value.

该函数将需要用公共参数替换选择器主体参数的所有实例.然后,它将用不同选择器的相应主体替换结果函数的两个参数.然后,我们将所有内容包装在一个lambda中.

The function will need to replace all instances of the parameters of the selectors' body with a common parameter. It will then replace the two parameters of the result function with the corresponding bodies of the different selectors. Then we just wrap all of that up in a lambda.

public static Expression<Func<T, TResult>> Combine
    <T, TIntermediate1, TIntermediate2, TResult>(
this Expression<Func<T, TIntermediate1>> first,
Expression<Func<T, TIntermediate2>> second,
Expression<Func<TIntermediate1, TIntermediate2, TResult>> resultSelector)
{
    var param = Expression.Parameter(typeof(T));
    var body = resultSelector.Body.Replace(
            resultSelector.Parameters[0],
            first.Body.Replace(first.Parameters[0], param))
        .Replace(
            resultSelector.Parameters[1],
            second.Body.Replace(second.Parameters[0], param));
    return Expression.Lambda<Func<T, TResult>>(body, param);
}

这使用以下方法将一个表达式的所有实例替换为另一个:

This uses the following method to replace all instances of one expression with another:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

现在我们可以编写以下内容:

Now we can write the following:

Expression<Func<Product, string>> name = p => p.Name;
Expression<Func<Product, string>> createdAt = p => p.createdAt.ToString();

Expression<Func<Product, string>> aggregate = 
    Combine(name, createdAt, (a, b) => a + "," + b);

它实际上比您的模型要简单一些,因为结果选择器不需要知道有关其输入如何生成的任何信息,也不必知道它们实际上是选择器.该解决方案还允许每个选择器选择不同的类型,并使它们与结果有所不同,这仅仅是因为在将要推断泛型参数时,添加所有这些选择并没有实际成本.

It actually comes out a bit simpler than in your mockup, as the result selector doesn't need to know anything about how its inputs are generated, or that they're actually selectors. This solution also allows for each selector to select out different types, and for them to differ from the result, simply because there's no real cost to adding all of this when the generic arguments are just going to be inferred.

有了这个适当的位置,您就可以轻松聚合任意数量的选择器,只要您施加了适当的类型限制即可.

And with this in place you can even easily aggregate an arbitrary number of selectors, given the type restrictions you've put in place:

IEnumerable<Expression<Func<Product, string>>> selectors = new[]{
    name,
    createdAt,
    name,
};

var finalSelector = selectors.Aggregate((first, second) =>
    Combine(first, second, (a, b) => a + "," + b));

例如,这将使您拥有一个params方法,该方法可以接受任意数量的选择器(具有相同的输入和输出类型),并且能够将所有结果汇总在一起.

This would let you, for example, have a params method accepting any number of selectors (of a common input and output type) and be able to aggregate all of their results together.

这篇关于使用LINQ to SQL的表达式树连接的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆