[Test] publicvoidExpression00() { Expression<Func<int, bool>> filter = x => x >= 1 && x < 5; var re = Enumerable.Range(0, 10).AsQueryable() .Where(filter).ToList(); var expectation = Enumerable.Range(1, 4); re.Should().BeEquivalentTo(expectation); }
有个叫盘古的巨人在这混沌之中
Expression 右侧是一个 Lambda ,所以可以捕获上下文中的变量。
这样你便可以把 minValue 和 maxValue 单独定义出来。
于是乎你可以从其他地方来获取 minValue 和 maxValue 来改变 filter。
1 2 3 4 5 6 7 8 9 10 11
[Test] publicvoidExpression01() { var minValue = 1; var maxValue = 5; Expression<Func<int, bool>> filter = x => x >= minValue && x < maxValue; var re = Enumerable.Range(0, 10).AsQueryable() .Where(filter).ToList(); var expectation = Enumerable.Range(1, 4); re.Should().BeEquivalentTo(expectation); }
他睡了一万八千年也都不用上班
那既然这样,我们也可以使用一个方法来创建 Expression。
这个方法,实际上就可以认为是这个 Expression 的工厂方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
[Test] publicvoidExpression02() { var filter = CreateFilter(1, 5); var re = Enumerable.Range(0, 10).AsQueryable() .Where(filter).ToList(); var expectation = Enumerable.Range(1, 4); re.Should().BeEquivalentTo(expectation);
Expression<Func<int, bool>> CreateFilter(int minValue, int maxValue) { return x => x >= minValue && x < maxValue; } }
有一天盘古突然醒了但天还没亮
那可以使用 minValue 和 maxValue 作为参数来制作工厂方法,那么用委托当然也可以。
于是,我们可以把左边和右边分别定义成两个 Func,从而由外部来决定左右具体的比较方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
[Test] publicvoidExpression03() { var filter = CreateFilter(x => x >= 1, x => x < 5); var re = Enumerable.Range(0, 10).AsQueryable() .Where(filter).ToList(); var expectation = Enumerable.Range(1, 4); re.Should().BeEquivalentTo(expectation);
[Test] publicvoidExpression04() { var filter = CreateFilter(x => x >= 1, x => x < 5); var re = Enumerable.Range(0, 10).AsQueryable() .Where(filter).ToList(); var expectation = Enumerable.Range(1, 4); re.Should().BeEquivalentTo(expectation);
Expression<Func<int, bool>> CreateFilter(Expression<Func<int, bool>> leftFunc, Expression<Func<int, bool>> rightFunc) { // x var pExp = Expression.Parameter(typeof(int), "x"); // (a => leftFunc(a))(x) var leftExp = Expression.Invoke(leftFunc, pExp); // (a => rightFunc(a))(x) var rightExp = Expression.Invoke(rightFunc, pExp); // (a => leftFunc(a))(x) && (a => rightFunc(a))(x) var bodyExp = Expression.AndAlso(leftExp, rightExp); // x => (a => leftFunc(a))(x) && (a => rightFunc(a))(x) var resultExp = Expression.Lambda<Func<int, bool>>(bodyExp, pExp); return resultExp; } }
只听枯叉一声黑暗渐渐地就分开
但是,上面的方法,其实可以在优化一下。避免对左右表达式的直接调用。
使用一个叫做 Unwrap 的方法,可以将 Lambda Expression 解构成只包含 Body 部分的表达式。
[Test] publicvoidExpression05() { var filter = CreateFilter(x => x >= 1, x => x < 5); var re = Enumerable.Range(0, 10).AsQueryable() .Where(filter).ToList(); var expectation = Enumerable.Range(1, 4); re.Should().BeEquivalentTo(expectation);
Expression<Func<int, bool>> CreateFilter(Expression<Func<int, bool>> leftFunc, Expression<Func<int, bool>> rightFunc) { // x var pExp = Expression.Parameter(typeof(int), "x"); // leftFunc(x) var leftExp = leftFunc.Unwrap(pExp); // rightFunc(x) var rightExp = rightFunc.Unwrap(pExp); // leftFunc(x) && rightFunc(x) var bodyExp = Expression.AndAlso(leftExp, rightExp); // x => leftFunc(x) && rightFunc(x) var resultExp = Expression.Lambda<Func<int, bool>>(bodyExp, pExp); return resultExp; } }
[Test] publicvoidExpression06() { var filter = JoinSubFilters(Expression.AndAlso, x => x >= 1, x => x < 5); var re = Enumerable.Range(0, 10).AsQueryable() .Where(filter).ToList(); var expectation = Enumerable.Range(1, 4); re.Should().BeEquivalentTo(expectation);
Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner, params Expression<Func<int, bool>>[] subFilters) { // x var pExp = Expression.Parameter(typeof(int), "x"); var result = subFilters[0]; foreach (var sub in subFilters[1..]) { var leftExp = result.Unwrap(pExp); var rightExp = sub.Unwrap(pExp); var bodyExp = expJoiner(leftExp, rightExp);
result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp); }
[Test] publicvoidExpression07() { var filter = JoinSubFilters(Expression.AndAlso, CreateMinValueFilter(1), x => x < 5); var re = Enumerable.Range(0, 10).AsQueryable() .Where(filter).ToList(); var expectation = Enumerable.Range(1, 4); re.Should().BeEquivalentTo(expectation);
Expression<Func<int, bool>> CreateMinValueFilter(int minValue) { return x => x >= minValue; }
Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner, params Expression<Func<int, bool>>[] subFilters) { // x var pExp = Expression.Parameter(typeof(int), "x"); var result = subFilters[0]; foreach (var sub in subFilters[1..]) { var leftExp = result.Unwrap(pExp); var rightExp = sub.Unwrap(pExp); var bodyExp = expJoiner(leftExp, rightExp);
result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp); }
[Test] publicvoidExpression08() { var filter = JoinSubFilters(Expression.AndAlso, CreateMinValueFilter(1), x => x < 5); var re = Enumerable.Range(0, 10).AsQueryable() .Where(filter).ToList(); var expectation = Enumerable.Range(1, 4); re.Should().BeEquivalentTo(expectation);
Expression<Func<int, bool>> CreateMinValueFilter(int minValue) { // x var pExp = Expression.Parameter(typeof(int), "x"); // minValue var rightExp = Expression.Constant(minValue); // x >= minValue var bodyExp = Expression.GreaterThanOrEqual(pExp, rightExp); var result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp); return result; }
Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner, params Expression<Func<int, bool>>[] subFilters) { // x var pExp = Expression.Parameter(typeof(int), "x"); var result = subFilters[0]; foreach (var sub in subFilters[1..]) { var leftExp = result.Unwrap(pExp); var rightExp = sub.Unwrap(pExp); var bodyExp = expJoiner(leftExp, rightExp);
result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp); }
return result; } }
那么看来盘古也吃不了上班的苦
那既然都用了 Expression 来创建子表达式了,那就干脆再做一点点改进,把x => x < 5也做成从工厂方法获取。
[Test] publicvoidExpression09() { var filter = JoinSubFilters(Expression.AndAlso, CreateValueCompareFilter(Expression.GreaterThanOrEqual, 1), CreateValueCompareFilter(Expression.LessThan, 5)); var re = Enumerable.Range(0, 10).AsQueryable() .Where(filter).ToList(); var expectation = Enumerable.Range(1, 4); re.Should().BeEquivalentTo(expectation);
Expression<Func<int, bool>> CreateValueCompareFilter(Func<Expression, Expression, Expression> comparerFunc, int rightValue) { var pExp = Expression.Parameter(typeof(int), "x"); var rightExp = Expression.Constant(rightValue); var bodyExp = comparerFunc(pExp, rightExp); var result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp); return result; }
Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner, params Expression<Func<int, bool>>[] subFilters) { // x var pExp = Expression.Parameter(typeof(int), "x"); var result = subFilters[0]; foreach (var sub in subFilters[1..]) { var leftExp = result.Unwrap(pExp); var rightExp = sub.Unwrap(pExp); var bodyExp = expJoiner(leftExp, rightExp);
result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp); }
return result; } }
所以如果可以不做这需求就别搞
最后,我们在把子表达式的创建通过一点点小技巧。通过外部参数来决定。就基本完成了一个多 And 的值比较查询条件的动态构建。
Expression<Func<int, bool>> CreateValueCompareFilter(Func<Expression, Expression, Expression> comparerFunc, int rightValue) { var pExp = Expression.Parameter(typeof(int), "x"); var rightExp = Expression.Constant(rightValue); var bodyExp = comparerFunc(pExp, rightExp); var result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp); return result; }
Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner, params Expression<Func<int, bool>>[] subFilters) { // x var pExp = Expression.Parameter(typeof(int), "x"); var result = subFilters[0]; foreach (var sub in subFilters[1..]) { var leftExp = result.Unwrap(pExp); var rightExp = sub.Unwrap(pExp); var bodyExp = expJoiner(leftExp, rightExp);
result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp); }