如何在组合模式下避免表达式树过滤中的循环依赖?
你的临时方案可行性验证
你的临时方案完全可行,它通过为特定运算符创建专属接口(如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:提取核心复用逻辑到独立服务
将运算符的核心表达式构建逻辑抽离为独立的无依赖服务,让所有运算符提供者依赖这些底层服务,而非互相依赖。
示例步骤:
- 定义核心逻辑接口与实现:
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); } }
- 修改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); } }
- 修改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

