尝试创建一个lambda来过滤linq中带有附加完整检查的实体中的日期

时间:2019-09-04 19:46:09

标签: c# linq lambda

不久前,我发布了问题Trying to create a morethan, equal or greaterthan dynamic filter for dates in linq,该问题很有帮助,但是我现在正尝试添加另一张支票,以使lambda现在看起来像这样

(left, right, equality, isCompleteCheck) => IsCompleteCheck == false | left equality right && IsCompleteCheck == true

我写了这段代码:

    public static IQueryable<TSource> FilterCompleteDateComparison<TSource>(
        this IQueryable<TSource> source,
        Expression<Func<TSource, DateTime?>> left,
        DateTime? right,
        string equality,
        Expression<Func<TSource, bool>> isCompleteCheck)
    {
        //                                                                        CombinedExpression 
        //                                                FirstExpression        Comparison Expression     SecondExpression
        // (left, right, equality, isCompleteCheck) => IsCompleteCheck == false | left equality right && IsCompleteCheck == true

        if (right == null || string.IsNullOrWhiteSpace(equality))
            return source;

        Expression IsCompleteCheckExpression = isCompleteCheck.Body;
        BinaryExpression FirstExpression = Expression.Equal(IsCompleteCheckExpression, Expression.Constant(false, typeof(bool)));

        Expression LeftExpression = left.Body;
        Expression RightParameter = Expression.Constant(right, typeof(DateTime?));

        BinaryExpression ComparisonExpression = null;
        switch (equality)
        {
            case "lessthan":
                ComparisonExpression = Expression.LessThan(LeftExpression, RightParameter);
                break;
            case "equal":
                ComparisonExpression = Expression.Equal(LeftExpression, RightParameter);
                break;
            case "morethan":
                ComparisonExpression = Expression.GreaterThan(LeftExpression, RightParameter);
                break;
            default:
                throw new Exception(String.Format("Equality {0} not recognised.", equality));
        }

        BinaryExpression SecondExpression = Expression.Equal(IsCompleteCheckExpression, Expression.Constant(true, typeof(bool)));
        BinaryExpression CombinedExpression = Expression.Or(FirstExpression, Expression.And(ComparisonExpression, SecondExpression));

        return source.Where(Expression.Lambda<Func<TSource, bool>>(CombinedExpression, left.Parameters));
    }

可以被

调用
BaseQuery.FilterCompleteDateComparison<SessionModel>(x => x.StartDate, DateValidated.Date, dateEquality, y => y.IsComplete);

但是如何为lambda提供IsCompleteCheckExpression参数?我试图将IsCompleteCheckExpression参数简单地连接到leftexpression参数上,并将其提供给lambda,但是错误表明属性太多。

有人有什么想法吗?

1 个答案:

答案 0 :(得分:1)

您需要使用一个ExpressionVisitor,可以将isCompleteCheck中的lambda参数更改为left中的lambda参数,以便将其用于两个lambda求值。通常将其命名为Replace

public static class ExpressionExt {
    /// <summary>
    /// Replaces an Expression (reference Equals) with another Expression
    /// </summary>
    /// <param name="orig">The original Expression.</param>
    /// <param name="from">The from Expression.</param>
    /// <param name="to">The to Expression.</param>
    /// <returns>Expression with all occurrences of from replaced with to</returns>
    public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
}

/// <summary>
/// ExpressionVisitor to replace an Expression (that is Equals) with another Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
    readonly Expression from;
    readonly Expression to;

    public ReplaceVisitor(Expression from, Expression to) {
        this.from = from;
        this.to = to;
    }

    public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}

鉴于此,对方法的更改非常简单。我还简化了您的测试逻辑(我的一般规则说,永远不要对布尔值进行测试,并且要进行DRY,因此t1 == false || t2 && t1 == true应该为!t1 || t2)。我还使用了Dictionary而不是switch来制作更简洁/更干燥且易于扩展的比较表达式。

static Dictionary<string, Func<Expression, Expression, BinaryExpression>> ComparisonOps = new Dictionary<string, Func<Expression, Expression, BinaryExpression>> {
    { "lessthan", Expression.LessThan },
    { "equal", Expression.Equal },
    { "morethan", Expression.GreaterThan },
};

public static IQueryable<TSource> FilterCompleteDateComparison<TSource>(
      this IQueryable<TSource> source,
      Expression<Func<TSource, DateTime?>> left,
      DateTime? right,
      string equality,
      Expression<Func<TSource, bool>> isCompleteCheck) {
    //                                                            CombinedExpression 
    //                                                      FirstExpression    Comparison Expression
    // (left, right, equality, isCompleteCheck) => s => !IsCompleteCheck(s) || left(s) equality right

    if (right == null || string.IsNullOrWhiteSpace(equality))
        return source;

    var IsCompleteCheckExpression = isCompleteCheck.Body.Replace(isCompleteCheck.Parameters[0], left.Parameters[0]);
    var FirstExpression = Expression.Not(IsCompleteCheckExpression);

    var LeftExpression = left.Body;
    var RightParameter = Expression.Constant(right, typeof(DateTime?));

    if (!ComparisonOps.TryGetValue(equality, out var op))
            throw new Exception(String.Format("Equality {0} not recognised.", equality));

    var ComparisonExpression = op(LeftExpression, RightParameter);
    var CombinedExpression = Expression.Or(FirstExpression, ComparisonExpression);

    return source.Where(Expression.Lambda<Func<TSource, bool>>(CombinedExpression, left.Parameters));
}