EF Core 8:寻求ThenInclude中动态设置Where条件的优雅解决方案
问题分析与解决方案
错误原因
你遇到的错误根源很明确:错误地将表达式树编译成了委托。combinedSpec.ToExpression()返回的是Expression<Func<Address, bool>>(EF Core需要的表达式树,用于生成SQL),但你调用了.Compile()把它转换成了Func<Address, bool>(仅能在内存中执行的委托),而EF Core的IQueryable.Where方法要求传入表达式树才能解析为SQL,因此抛出参数类型不匹配的异常。
优雅解决方案
核心思路是始终保留表达式树不编译,同时简化Specification的组合逻辑,让代码更简洁易维护。
1. 简化Specification组合逻辑
用LINQ聚合操作替代繁琐的空判断,自动组合所有非空的Address查询条件:
// 收集所有有效的Address Specification var addressSpecs = new List<Specification<Address>>(); if (!string.IsNullOrEmpty(_city)) addressSpecs.Add(new AddressByCitySpecification(_city)); if (!string.IsNullOrEmpty(_street)) addressSpecs.Add(new AddressByStreetSpecification(_street)); if (_zip > 0) addressSpecs.Add(new AddressByZipSpecification(_zip)); // 用Or逻辑聚合所有Specification var combinedAddressSpec = addressSpecs.Aggregate((current, next) => current.Or(next));
2. 修正Include的Where条件
直接传入表达式树,去掉.Compile():
if (combinedAddressSpec != null) { AddInclude(p => p.AddressList.Where(combinedAddressSpec.ToExpression())); } else { AddInclude(p => p.AddressList); }
3. 完整修正代码
public class PersonByAddressCityOrZip : Specification<Person> { private readonly string _city; private readonly int _zip; private readonly string _street; public PersonByAddressCityOrZip(string city, int zip, string street) { _city = city; _zip = zip; _street = street; // 收集有效查询条件 var addressSpecs = new List<Specification<Address>>(); if (!string.IsNullOrEmpty(_city)) addressSpecs.Add(new AddressByCitySpecification(_city)); if (!string.IsNullOrEmpty(_street)) addressSpecs.Add(new AddressByStreetSpecification(_street)); if (_zip > 0) addressSpecs.Add(new AddressByZipSpecification(_zip)); // 组合查询条件 var combinedAddressSpec = addressSpecs.Aggregate((current, next) => current.Or(next)); // 设置Include过滤 if (combinedAddressSpec != null) { AddInclude(p => p.AddressList.Where(combinedAddressSpec.ToExpression())); } else { AddInclude(p => p.AddressList); } } public bool IsSatisfied(Person item) { // 可选:实现内存对象的验证逻辑 var conditions = new List<Func<Address, bool>>(); if (!string.IsNullOrEmpty(_city)) conditions.Add(a => a.City.Contains(_city)); if (!string.IsNullOrEmpty(_street)) conditions.Add(a => a.Street.Contains(_street)); if (_zip > 0) conditions.Add(a => a.Zip == _zip); return conditions.Any() ? item.AddressList.Any(a => conditions.Any(c => c(a))) : true; } public override Expression<Func<Person, bool>> ToExpression() { // 构建Person级别的查询表达式,和Include条件保持一致 var addressConditions = new List<Expression<Func<Address, bool>>>(); if (!string.IsNullOrEmpty(_city)) addressConditions.Add(a => a.City.Contains(_city)); if (!string.IsNullOrEmpty(_street)) addressConditions.Add(a => a.Street.Contains(_street)); if (_zip > 0) addressConditions.Add(a => a.Zip == _zip); var combinedAddressExpr = addressConditions.Aggregate((curr, next) => curr.Or(next)); return person => combinedAddressExpr != null ? person.AddressList.Any(combinedAddressExpr) : true; } }
额外优化
给Specification<T>添加静态组合方法,避免重复处理空列表:
public abstract class Specification<T> { // 其他已有代码... public static Specification<T> CombineOr(IEnumerable<Specification<T>> specs) { var validSpecs = specs.Where(s => s != null).ToList(); return validSpecs.Any() ? validSpecs.Aggregate((a, b) => a.Or(b)) : null; } }
之后组合逻辑可以简化为:
var combinedAddressSpec = Specification<Address>.CombineOr(addressSpecs);
内容的提问来源于stack exchange,提问作者axur Sdaffv
相关产品推荐
相关产品推荐

