提问人:Parsa99 提问时间:11/4/2023 更新时间:11/7/2023 访问量:70
C# 合并/合并表达式
C# merge/combine expressions
问:
使用以下示例类(可以映射到数据库表):
class Package
{
public string Name { get; set; }
public decimal Price { get; set; }
public bool Enabled { get; set; }
public static Expression<Func<Package, bool>> IsActive =>
x => x.Enabled;
}
class Schedule
{
public DateTimeOffset Start { get; set; }
public DateTimeOffset End { get; set; }
public bool Enabled { get; set; }
public static Expression<Func<Schedule, bool>> IsActive =>
x => x.Enabled && x.Start > DateTimeOffset.Now;
}
以及以下内容(可以是联接表):
class SchedulePackage
{
public Package Package { get; init; }
public Schedule Schedule { get; init; }
}
如何将这两个表达式合并?活动是指其包和计划都处于活动状态(无论这意味着什么)。因此,为了避免代码重复,最好重用每个实体的逻辑。AndAlso
SchedulePackage
IsActive
假设我有以下几点:
public static Expression<Func<SchedulePackage, bool>> IsActive =>
x => x.Package.Enabled && x.Schedule.Enabled && x.Schedule.Start > DateTimeOffset.Now;
现在,如果我更新包逻辑
public static Expression<Func<Package, bool>> IsActive =>
x => x.Enabled && x.Price > 0;
我也必须更新:SchedulePackage
public static Expression<Func<SchedulePackage, bool>> IsActive =>
x => x.Package.Enabled && x.Package.Price > 0 && x.Schedule.Enabled && x.Schedule.Start > DateTimeOffset.Now;
答:
2赞
dbc
11/7/2023
#1
在实体框架(或其他 LINQ to Entities 应用程序)的上下文中,一个表达式通常不可能调用另一个表达式,原因如“LINQ to Entities 不支持 LINQ 表达式节点类型'Invoke'”中所述 - stumped!。相反,您可以做的是子类化 ExpressionVisitor
以创建 和 的主体的修改副本,这些副本将 和 作为输入,然后使用二进制表达式组合它们以获得最终结果。Schedule.IsActive
Package.IsActive
SchedulePackage.Schedule
SchedulePackage.Package
&&
为此,请首先创建以下用于组合函数表达式的扩展方法:
public static partial class ExpressionExtensions
{
// Compose two Func<Tx, Ty> expressions with compatible generic parameters into a third.
public static Expression<Func<T1, TResult>> Compose<T1, T2, TResult>(this Expression<Func<T2, TResult>> outer, Expression<Func<T1, T2>> inner) =>
Expression.Lambda<Func<T1, TResult>>(
new ParameterReplacer((outer.Parameters[0], inner.Body)).Visit(outer.Body),
false, inner.Parameters[0]);
// Compose a Func<T2, T3, TResult> expression with compatible Func<T1, T2> and Func<T1, T3> expressions to obtain a Func<T1, TResult> expression.
public static Expression<Func<T1, TResult>> Compose<T1, T2, T3, TResult>(this Expression<Func<T2, T3, TResult>> outer, Expression<Func<T1, T2>> inner1, Expression<Func<T1, T3>> inner2)
{
var inner2body = new ParameterReplacer((inner2.Parameters[0], (Expression)inner1.Parameters[0])).Visit(inner2.Body);
return Expression.Lambda<Func<T1, TResult>>(
new ParameterReplacer((outer.Parameters[0], inner1.Body), (outer.Parameters[1], inner2body)).Visit(outer.Body),
false, inner1.Parameters[0]);
}
}
class ParameterReplacer : ExpressionVisitor
{
// Replace formal parameters (e.g. of a lambda body) with some containing expression in scope.
readonly Dictionary<ParameterExpression, Expression> parametersToReplace;
public ParameterReplacer(params (ParameterExpression parameter, Expression replacement) [] parametersToReplace) =>
this.parametersToReplace = parametersToReplace.ToDictionary(p => p.parameter, p => p.replacement);
protected override Expression VisitParameter(ParameterExpression p) =>
parametersToReplace.TryGetValue(p, out var e) ? e : base.VisitParameter(p);
}
现在你可以写如下:SchedulePackage.IsActive
static Lazy<Expression<Func<SchedulePackage, bool>>> IsActiveExpression = new(static () => {
var left = Package.IsActive.Compose((SchedulePackage sp) => sp.Package);
var right = Schedule.IsActive.Compose((SchedulePackage sp) => sp.Schedule);
Expression<Func<bool, bool, bool>> binary = (b1, b2) => b1 && b2;
return binary.Compose(left, right);
});
public static Expression<Func<SchedulePackage, bool>> IsActive => IsActiveExpression.Value;
生成的表达式如下所示:
sp => (sp.Package.Enabled AndAlso (sp.Schedule.Enabled AndAlso (sp.Schedule.Start > DateTimeOffset.Now)))
请注意,内部表达式已内联,而不是调用。IsActive
我懒洋洋地构造了一次表达式,并纯粹出于性能原因重用它。
在这里演示小提琴。
评论
IsActive
public bool IsActive => Enabled && Start > DateTimeOffset.Now
and
ExpressionVisitor
创建 的正文的修改副本,并将参数表达式替换为其他表达式,例如访问 的表达式。有关演示,请参阅 dotnetfiddle.net/KueMiX。这回答了你的问题吗?Schedule.IsActive
Package.IsActive
SchedulePackage