在 linq-2-sql 表达式中包含 lambda

Include lambda in linq-2-sql expression

我',试图重构一些 linq-2-sql 魔法,但有些事情我显然无法理解。该代码使用此谓词构建器

public static class PredicateBuilder {
    public static Expression<Func<T, bool>> True<T>() {
        return f => true;
    }

    public static Expression<Func<T, bool>> False<T>() {
        return f => false;
    }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
                                                  Expression<Func<T, bool>> expr2) {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>
            (Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
                                                   Expression<Func<T, bool>> expr2) {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>
            (Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
    }
}

使用方法如下:

predicate =
    predicate.And(
        f =>
            f.Created != null
                ? args.Dato1.Date < f.Created.Value.Date &&
                  f.Created.Value.Date < args.Dato2.Date
                : false);

很多。

所以我在想也许可以通过以下方式使用更具描述性的名称和更少的行:

private Expression<Func<DAL.Faktura, bool>> beforeInclusiveExpression(Func<DAL.Faktura, DateTime?> getDateTime, DateTime date) {
    return f => getDateTime(f).HasValue && getDateTime(f).Value.Date <= date.Date;
}

然后按以下方式构建谓词:

predicate =
    predicate
        .And(beforeInclusiveExpression(f => f.Created, d.Dato2)
        .And(afterInclusiveExpression(f => f.Created, d.Dato1);

但这不起作用,它只会抛出以下错误Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.。我知道这是因为 linq-2-sql 提供者不知道如何处理 lambda,但是我怎样才能将它转化为使我能够重构为更易于维护的东西。

为了使 beforeInclusiveExpression 方法适用于 linq-to-sql,您应该更改参数

Func<DAL.Faktura, DateTime?> getDateTime 

Expression<Func<DAL.Faktura, DateTime?>> getDateTime

但是你不能简单地调用它,你必须将所有内容翻译成 Expression 并创建表达式树。

尝试:

private static Expression<Func<DAL.Faktura, bool>> beforeInclusiveExpression(Expression<Func<DAL.Faktura, DateTime?>> getDateTime, DateTime date)
{
    // return f => getDateTime(f).HasValue && getDateTime(f).Value.Date <= date.Date;
    var parameterF = Expression.Parameter(typeof(DAL.Faktura), "f");                          // f
    var getDateTimeInvocation = Expression.Invoke(getDateTime, parameterF);                   // getDateTime(f)
    var getDateTime_HasValue = Expression.Property(getDateTimeInvocation, "HasValue");        // getDateTime(f).HasValue
    var getDateTime_Value = Expression.Property(getDateTimeInvocation, "Value");              // getDateTime(f).Value
    var getDateTime_Value_Date = Expression.Property(getDateTime_Value, "Date");              // getDateTime(f).Value.Date

    return Expression.Lambda<Func<DAL.Faktura, bool>>(Expression.AndAlso(getDateTime_HasValue,// getDateTime(f).HasValue &&
        Expression.LessThanOrEqual(getDateTime_Value_Date, Expression.Constant(date.Date))),  // getDateTime(f).Value.Date <= date.Date
        parameterF);                                                                          
}

我在谷歌搜索了一段时间后终于找到了答案。 Dzienny 的回答似乎有效,但依赖于 Invoke 而原始答案无效。对表达式和linq-2-sql不太舒服,我想保持重构尽可能接近原始

退一步说,我们有一个选择器、一个参数(日期时间)和两者之间的运算符。这给了我们这个签名

Expression<Func<DAL.Faktura, DateTime?>>, DateTime?, Func<Expression, Expression, BinaryExpression> -> Expression<Func<DAL.Faktura, bool>>

我们必须从中创建一个新表达式:

private Expression<Func<DAL.Faktura, bool>> dateTimeOperatorExpression(
    Expression<Func<DAL.Faktura, DateTime?>> selector, DateTime? date,
    Func<Expression, Expression, BinaryExpression> func) {

    //We only need the Date part of the DateTime. This lambda does the trick.
    var dateSelector = (Expression<Func<DateTime?, DateTime>>) (dt => dt.Value.Date);
    //f.Created != null
    var dateTimeNotNullPredicate = Expression.NotEqual(selector.Body,
        Expression.Constant(null, typeof (DateTime?)));

    //This transforms dateSelector: dt => dt.Value.Date
    //and selector: f => f.Created
    //into a lambda expression: f => f.Created.Value.Date
    var swap = new SwapVisitor(dateSelector.Parameters[0], selector.Body);
    var selectedPropertyDate = Expression.Lambda<Func<DAL.Faktura, DateTime>>(swap.Visit(dateSelector.Body),
        selector.Parameters);

    //Apply the supplied operator, here Expression.GreaterThanOrEqual or
    //Expression.LessThanOrEqual
    var predicate = func(selectedPropertyDate.Body, Expression.Constant(date.Value.Date, typeof (DateTime)));

    var combined = Expression.And(dateTimeNotNullPredicate, predicate);
    return Expression.Lambda<Func<DAL.Faktura, bool>>(combined, selector.Parameters);
}

ExpressionVisitor帮手,不知道原作者是谁,不过我在SO上找到了。

class SwapVisitor : ExpressionVisitor {
    private readonly Expression from, to;
    public SwapVisitor(Expression from, Expression to) {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node) {
        return node == from ? to : base.Visit(node);
    }
}

所以,原代码

predicate.And(
    f =>
        f.Created != null
            ? d.Dato1.Date < f.Created.Value.Date &&
              f.Created.Value.Date < d.Dato2.Date
            : false);

创建了一个大致如下所示的表达式:

expr2
{ f => 
    if (f.Created != Convert(null)) 
    (
        (value(MyType+<>c__DisplayClass36).d.Dato1.Date <= f.Created.Value.Date)
        AndAlso 
        (f.Created.Value.Date <= value(MyType+<>c__DisplayClass36).d.Dato2.Date)
    )
    else
        (False)
}   System.Linq.Expressions.Expression<System.Func<DAL.Faktura,bool>>

虽然上面的代码和这个方法

private Expression<Func<DAL.Faktura, bool>> betweenInclusiveExpression(
    Expression<Func<DAL.Faktura, DateTime?>> selector, DateTime? beginingDateTime, DateTime? endDateTime) {
    var afterPredicate = dateTimeOperatorExpression(selector, beginingDateTime, Expression.GreaterThanOrEqual);
    var beforePredicate = dateTimeOperatorExpression(selector, endDateTime, Expression.LessThanOrEqual);

    var combined = Expression.AndAlso(afterPredicate.Body, beforePredicate.Body);
    return Expression.Lambda<Func<DAL.Faktura, bool>>(combined, selector.Parameters);
}

产生这个表达式:

expr2
{
    f => 
        (
            ((f.Created != null) AndAlso (f.Created.Value.Date >= 07-07-2015 00:00:00))
            AndAlso 
            ((f.Created != null) AndAlso (f.Created.Value.Date <= 07-07-2015 00:00:00))
        )
}   System.Linq.Expressions.Expression<System.Func<DAL.Faktura,bool>>

我唯一不确定的是,固定日期是否与原始日期不同。我认为,一旦 linq-2-sql 提供程序将表达式转换为 SQL,它就会捕获变量。换句话说,它现在发生得更早了。