You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在组合模式下避免表达式树过滤中的循环依赖?

解决方案验证与推荐

你的临时方案可行性验证

你的临时方案完全可行,它通过为特定运算符创建专属接口(如IEqualsOperatorExpressionProvider),打破了OneOfOperatorProvider对IEnumerable<IFilterOperatorExpressionProvider>的依赖——此时OneOf只依赖它真正需要的Equals运算符的专属接口,容器解析时不会将OneOf自身纳入依赖集合,从而避免循环依赖。

但这个方案的缺点也很明显:如果后续需要复用更多运算符(比如Between要复用GreaterThanOrEquals和LessThanOrEquals),你需要不断新增类似IGreaterThanOrEqualsOperatorExpressionProvider的专属接口,会导致接口数量膨胀,增加维护成本。

更优雅的替代方案

方案1:延迟注入(Lazy Resolution)

通过Func<T>或Lazy<T>延迟获取运算符提供者集合,避免构造时立即解析所有依赖,从而打破循环。

示例代码:

public class OneOfOperatorProvider : IFilterOperatorExpressionProvider
{
    // 注入工厂函数,延迟获取运算符集合
    private readonly Func<IEnumerable<IFilterOperatorExpressionProvider>> _operatorProvidersFactory;

    public OneOfOperatorProvider(Func<IEnumerable<IFilterOperatorExpressionProvider>> operatorProvidersFactory)
    {
        _operatorProvidersFactory = operatorProvidersFactory;
    }

    public bool Supports(FilterOperators filterOperator, Type propertyType)
    {
        return filterOperator == FilterOperators.OneOf;
    }

    public Expression GetExpression(PropertyInfo propertyInfo, ParameterExpression parameter, object? value)
    {
        // 仅在需要时才解析运算符集合
        var allProviders = _operatorProvidersFactory();
        var equalsProvider = allProviders.First(p => p.Supports(FilterOperators.Equals, propertyType));

        // 复用Equals的逻辑构建OneOf表达式
        var values = (IEnumerable)value;
        var equalsExpressions = values.Cast<object>()
                                      .Select(v => equalsProvider.GetExpression(propertyInfo, parameter, v));
        return equalsExpressions.Aggregate(Expression.OrElse);
    }
}

.NET的DI容器会自动支持Func<IEnumerable<IFilterOperatorExpressionProvider>>的注入,无需额外配置。这种方式不需要新增接口,同时解决了循环依赖问题。

方案2:提取核心复用逻辑到独立服务

将运算符的核心表达式构建逻辑抽离为独立的无依赖服务,让所有运算符提供者依赖这些底层服务,而非互相依赖。

示例步骤:

  1. 定义核心逻辑接口与实现:
public interface IEqualityExpressionBuilder
{
    Expression BuildEquals(PropertyInfo propertyInfo, ParameterExpression parameter, object value);
}

public class EqualityExpressionBuilder : IEqualityExpressionBuilder
{
    public Expression BuildEquals(PropertyInfo propertyInfo, ParameterExpression parameter, object value)
    {
        var propertyExpr = Expression.Property(parameter, propertyInfo);
        var constantExpr = Expression.Constant(value, propertyInfo.PropertyType);
        return Expression.Equal(propertyExpr, constantExpr);
    }
}
  1. 修改Equals运算符提供者依赖该服务:
public class EqualsOperatorProvider : IFilterOperatorExpressionProvider
{
    private readonly IEqualityExpressionBuilder _equalityBuilder;

    public EqualsOperatorProvider(IEqualityExpressionBuilder equalityBuilder)
    {
        _equalityBuilder = equalityBuilder;
    }

    public bool Supports(FilterOperators filterOperator, Type propertyType)
    {
        return filterOperator == FilterOperators.Equals;
    }

    public Expression GetExpression(PropertyInfo propertyInfo, ParameterExpression parameter, object? value)
    {
        return _equalityBuilder.BuildEquals(propertyInfo, parameter, value);
    }
}
  1. 修改OneOf运算符提供者直接依赖该服务:
public class OneOfOperatorProvider : IFilterOperatorExpressionProvider
{
    private readonly IEqualityExpressionBuilder _equalityBuilder;

    public OneOfOperatorProvider(IEqualityExpressionBuilder equalityBuilder)
    {
        _equalityBuilder = equalityBuilder;
    }

    public bool Supports(FilterOperators filterOperator, Type propertyType)
    {
        return filterOperator == FilterOperators.OneOf;
    }

    public Expression GetExpression(PropertyInfo propertyInfo, ParameterExpression parameter, object? value)
    {
        var values = (IEnumerable)value;
        var equalsExpressions = values.Cast<object>()
                                      .Select(v => _equalityBuilder.BuildEquals(propertyInfo, parameter, v));
        return equalsExpressions.Aggregate(Expression.OrElse);
    }
}

这种方案彻底消除了运算符提供者之间的依赖,结构更清晰,易于扩展和测试,是最推荐的方案。

方案3:服务定位器(不推荐)

如果上述方案都不适用,可以通过服务定位器在需要时从容器中获取特定提供者,但这种方式会耦合DI容器,降低代码可测试性,仅作为最后备选:

public class OneOfOperatorProvider : IFilterOperatorExpressionProvider
{
    private readonly IServiceProvider _serviceProvider;

    public OneOfOperatorProvider(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public Expression GetExpression(PropertyInfo propertyInfo, ParameterExpression parameter, object? value)
    {
        var equalsProvider = _serviceProvider.GetServices<IFilterOperatorExpressionProvider>()
                                             .First(p => p.Supports(FilterOperators.Equals, propertyInfo.PropertyType));
        // 后续逻辑同前
    }
}

内容的提问来源于stack exchange,提问作者user26338095

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 06:42:16