使用包含多个参数的BinaryExpression创建谓词 [英] Create predicate with a BinaryExpression containing multiple parameters
问题描述
是否可以使用LambdaExpressions
动态生成这样的谓词?
is it possible to dynamically generate such a predicate using LambdaExpressions
?
Expression<Func<Test, bool>> predicate = t =>
t.Levels.Any(l =>
l.LevelDetails.Any( ld =>
ld.LevelDate > DbFunctions.AddDays(t.TestDate, 1)
)
);
只要内部BinaryExpression
中的参数相同或表达式的右侧部分是常数,就没有问题.但是示例表达式ld.LevelDate > DbFunctions.AddDays (t.TestDate, 1)
包含两个彼此独立的不同的ExpressionParameters
.我正在寻找的是这样的:
As long as the parameters in the inner BinaryExpression
are identical or the right part of the expression is constant, there is no problem. But the example expressionld.LevelDate > DbFunctions.AddDays (t.TestDate, 1)
contains two different ExpressionParameters
which are independent from each other. What I am looking for is something like this:
Expression<Func<LevelDetail, DateTime?>> left =
ld => ld.LevelDate;
Expression<Func<Test, DateTime?>> right =
t => DbFunctions.AddDays(t.TestDate, 1);
BinaryExpression expr =
Expression.GreaterThan(
((LambdaExpression)left).Body,
((LambdaExpression)right).Body
);
Expression<Func<Test, bool>> predicate = t =>
t.Levels.Any(l =>
l.LevelDetails.Any( **expr** )
);
class Test {
public DateTime TestDate { get; set; }
public virtual ICollection<Level> Levels { get; set; }
}
class Level {
public virtual ICollection<LevelDetail> LevelDetails { get; set; }
}
class LevelDetail {
public DateTime LevelDate { get; set; }
}
亲切的问候!
推荐答案
如@Matt Warren的答案所指出的,如果您希望合并lambda,则需要手工完成,并且需要设置正确的表达式参数
As pointed out in the answers by @Matt Warren if yow want to combine lambdas you will need to do it by hand and will need to set the correct expression parameters.
首先,您将需要一个ExpressionVisitor
可以替换您想要的节点:
Firstlly, you will need a ExpressionVisitor
that can replace node that you want:
private class SwapVisitor : ExpressionVisitor
{
public readonly Expression _from;
public readonly Expression _to;
public SwapVisitor(Expression from, Expression to)
{
_from = from;
_to = to;
}
public override Expression Visit(Expression node) => node == _from ? _to : base.Visit(node);
}
第二,您需要手工组合lambda:
Secondly, you will need to combine lambdas by hand:
private static Expression<Func<Test, bool>> CreatePredicate()
{
Expression<Func<LevelDetail, DateTime?>> left = ld => ld.LevelDate;
// I didn't include EF, so I did test it just use directly Test.TestDate
//Expression<Func<Test, DateTime?>> right = t => t.TestDate;
Expression<Func<Test, DateTime?>> right = t => DbFunctions.AddDays(t.TestDate, 1);
var testParam = Expression.Parameter(typeof(Test), "test_par");
var levelParam = Expression.Parameter(typeof(Level), "level_par");
var detailParam = Expression.Parameter(typeof(LevelDetail), "detail_par");
// Swap parameters for right and left operands to the correct parameters
var swapRight = new SwapVisitor(right.Parameters[0], testParam);
right = swapRight.Visit(right) as Expression<Func<Test, DateTime?>>;
var swapLeft = new SwapVisitor(left.Parameters[0], detailParam);
left = swapLeft.Visit(left) as Expression<Func<LevelDetail, DateTime?>>;
BinaryExpression comparer = Expression.GreaterThan(left.Body, right.Body);
var lambdaComparer = Expression.Lambda<Func<LevelDetail, bool>>(comparer, detailParam);
// Well, we created here the lambda for ld => ld.LevelDate > DbFunctions.AddDays(t.TestDate, 1)
var anyInfo = typeof(Enumerable).GetMethods().Where(info => info.Name == "Any" && info.GetParameters().Length == 2).Single();
// Will create **l.LevelDetails.Any(...)** in the code below
var anyInfoDetail = anyInfo.MakeGenericMethod(typeof(LevelDetail));
var anyDetailExp = Expression.Call(anyInfoDetail, Expression.Property(levelParam, "LevelDetails"), lambdaComparer);
var lambdaAnyDetail = Expression.Lambda<Func<Level, bool>>(anyDetailExp, levelParam);
// Will create **t.Levels.Any(...)** in the code below and will return the finished lambda
var anyInfoLevel = anyInfo.MakeGenericMethod(typeof(Level));
var anyLevelExp = Expression.Call(anyInfoLevel, Expression.Property(testParam, "Levels"), lambdaAnyDetail);
var lambdaAnyLevel = Expression.Lambda<Func<Test, bool>>(anyLevelExp, testParam);
return lambdaAnyLevel;
}
下面的代码包含此用法:
And the code below contains usage of this:
var predicate = CreatePredicate();
var levelDetail = new LevelDetail { LevelDate = new DateTime(2017, 08, 19) };
var level = new Level { LevelDetails = new List<LevelDetail> { levelDetail } };
var test = new Test { TestDate = new DateTime(2027, 08, 19), Levels = new List<Level> { level } };
var result = predicate.Compile()(test);
这篇关于使用包含多个参数的BinaryExpression创建谓词的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!