如何集成Expression <func <>&gt;清理我的Linq-to-Entity查询?</func <>

时间:2013-11-27 21:14:39

标签: c# asp.net linq entity-framework linq-to-sql

我想使用Expression<Func<>>方法来清理我的DTO Linq-to-Query Selects,以免它们比现有的增长。关于如何将它们集成到我自己的项目中,我仍然有点困惑。

一个例子:

public IQueryable<ExampleDTO> SelectDTO()
{
  var repository = new ExampleUDCRepository();

  return db.Example
           .Select(v => new ExampleDTO
           {
             ExampleID     = v.ExampleID,
             MasterGroupID = v.MasterGroupID,
             //...etc

             ExampleUDCs = db.ExampleUDCs
                             .Where(vudc => vudc.ExampleID == v.ExampleID)
                             .AsEnumerable()
                             .Select(vudc => new ExampleDCDTO
                             {
                               ExampleID    = vudc.ExampleID,
                               UDCHeadingID = vudc.UDCHeadingID,
                               UDCValue     = vudc.UDCValue
                             })
           });

我的一些其他DTO设置和返回方法甚至更大,更笨拙。

我真正想做的是这样的事情:

public IQueryable<ExampleDTO> SelectDTO()
{
  var repository = new ExampleUDCRepository();

  return db.Example
           .Select(v => new ExampleDTO
           {
             ExampleID     = v.ExampleID,
             MasterGroupID = v.MasterGroupID,
             //...etc

             ExampleUDCs = new ExampleUDCsRepository().SelectDTO(v);
             // SelectDTO(Example v) in that repository would call
             // any other SelectDTO it might need and so forth
           });

问题是,Linq不知道如何将这样的方法转换为SQL语句,这是Expression<Func<>>方法在传递这类事物方面发挥作用的地方。

我对Expression<Func<>>的理解是有限的,我还没有找到任何文件,这使得我正在努力做得更清楚。

Expression<Func<>>整合到我的DTO中的最佳方法是什么?

1 个答案:

答案 0 :(得分:2)

首先,我们需要一些辅助方法。我们将从这个简单的类开始,将一个表达式的所有实例替换为另一个:

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

接下来,我们将创建一个扩展方法来使用它:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

最后,我们将创建一个将两个表达式组合在一起的Combine方法。它将采用一个表达式来计算一个值的中间结果,然后另一个表达式使用第一个值和中间结果来确定最终结果。

public static Expression<Func<TFirstParam, TResult>>
    Combine<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TFirstParam, TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], param)
        .Replace(second.Parameters[1], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

接下来,我们可以定义在给定示例对象的情况下计算ExampleDCDTO对象的方法。它将直接提取您的上述内容,但不是返回IEnumerable<ExampleDCDTO>,而是需要返回一个将Example转换为这样一个序列的表达式:

public Expression<Func<Example, IEnumerable<ExampleDCDTO>>> SelectDTO()
{
    return v => db.ExampleUDCs.Where(vudc => vudc.ExampleID == v.ExampleID)
        .AsEnumerable()
        .Select(vudc => new ExampleDCDTO
        {
            ExampleID = vudc.ExampleID,
            UDCHeadingID = vudc.UDCHeadingID,
            UDCValue = vudc.UDCValue
        });
}

现在要把它们放在一起,我们可以调用这个SelectDTO方法来生成计算中间值的表达式,并使用另一个使用它的表达式Combine

public IQueryable<ExampleDTO> SelectDTO()
{
    ExampleUDCRepository repository = new ExampleUDCRepository();
    return db.Example
            .Select(repository.SelectDTO().Combine((v, exampleUDCs) =>
                new ExampleDTO()
                {
                    ExampleID = v.ExampleID,
                    MasterGroupID = v.MasterGroupID,
                    ExampleUDCs = exampleUDCs,
                }));
}

对于那些使用LINQKit的人来说,另一个选择是使用AsExpandable而不是所有的帮助方法。使用这种方法仍然需要创建返回SelectDTO的{​​{1}}方法,但您可以将结果组合起来:

Expression<Func<Example, IEnumerable<ExampleDCDTO>>>