C# 合并/组合表达式

问题描述 投票:0回答:1

使用以下示例类(可以映射到数据库表):

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;
c# expression expression-trees
1个回答
0
投票

在实体框架(或其他 LINQ to Entities 应用程序)的上下文中,一个表达式通常不可能调用另一个表达式,原因请参阅“LINQ to Entities 不支持 LINQ 表达式节点类型“Invoke” “ - 难住了!。相反,您可以做的是子类

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
表达式已被内联而不是调用。

我懒惰地构造一次表达式并重复使用它纯粹是出于性能原因。

演示小提琴在这里

© www.soinside.com 2019 - 2024. All rights reserved.