繁体   English   中英

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

经过一段时间的搜寻后,我终于找到了答案。 Dziennys的答案似乎有效,但是依赖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,它就会捕获变量。 换句话说,现在发生的情况稍微好一些。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM