如何让自定义Include/ThenInclude与EF Core原生实现行为一致?
解决自定义Include/ThenInclude中集合导航属性的重载匹配问题
要让自定义实现和EF Core原生行为一致,核心是让编译器能正确识别集合类型(List<B>/B[])到IEnumerable<B>的隐式转换,并匹配对应重载。以下是具体修改方案:
1. 调整IIncludableQueryable的泛型设计
确保接口的第二个泛型参数支持集合类型的抽象表示,而非具体实现:
public interface IIncludableQueryable<TEntity, TProperty> : IQueryable<TEntity> where TEntity : class { }
2. 修正Include扩展方法的重载
为集合类型导航属性单独定义重载,让List<B>/B[]能隐式匹配到IEnumerable<B>版本,返回的IIncludableQueryable使用抽象集合类型作为泛型参数:
// 集合类型导航属性的Include重载 public static IIncludableQueryable<TEntity, IEnumerable<TProperty>> Include<TEntity, TProperty>( this IQueryable<TEntity> source, Expression<Func<TEntity, IEnumerable<TProperty>>> navigationPropertyPath) where TEntity : class { // 自定义逻辑:记录导航属性、包装查询等 return new Includer<TEntity, IEnumerable<TProperty>>(source, navigationPropertyPath); } // 非集合类型导航属性的Include重载 public static IIncludableQueryable<TEntity, TProperty> Include<TEntity, TProperty>( this IQueryable<TEntity> source, Expression<Func<TEntity, TProperty>> navigationPropertyPath) where TEntity : class where TProperty : class { return new Includer<TEntity, TProperty>(source, navigationPropertyPath); }
3. 补全ThenInclude的集合专属重载
明确针对IEnumerable<TPreviousElement>的ThenInclude重载,让编译器能正确推断集合元素类型:
// 集合导航属性的ThenInclude重载(链式包含集合) public static IIncludableQueryable<TEntity, IEnumerable<TProperty>> ThenInclude<TEntity, TPreviousElement, TProperty>( this IIncludableQueryable<TEntity, IEnumerable<TPreviousElement>> source, Expression<Func<TPreviousElement, IEnumerable<TProperty>>> navigationPropertyPath) where TEntity : class { return new Includer<TEntity, IEnumerable<TProperty>>(source, navigationPropertyPath); } // 集合导航属性的ThenInclude重载(链式包含单个属性) public static IIncludableQueryable<TEntity, TProperty> ThenInclude<TEntity, TPreviousElement, TProperty>( this IIncludableQueryable<TEntity, IEnumerable<TPreviousElement>> source, Expression<Func<TPreviousElement, TProperty>> navigationPropertyPath) where TEntity : class where TProperty : class { return new Includer<TEntity, TProperty>(source, navigationPropertyPath); } // 非集合导航属性的ThenInclude重载 public static IIncludableQueryable<TEntity, TProperty> ThenInclude<TEntity, TPreviousProperty, TProperty>( this IIncludableQueryable<TEntity, TPreviousProperty> source, Expression<Func<TPreviousProperty, TProperty>> navigationPropertyPath) where TEntity : class where TPreviousProperty : class where TProperty : class { return new Includer<TEntity, TProperty>(source, navigationPropertyPath); }
4. 实现Includer类的通用支持
确保Includer能兼容抽象集合类型的泛型参数:
public class Includer<TEntity, TProperty> : IIncludableQueryable<TEntity, TProperty> where TEntity : class { private readonly IQueryable<TEntity> _source; public Expression Expression => _source.Expression; public Type ElementType => _source.ElementType; public IQueryProvider Provider => _source.Provider; public Includer(IQueryable<TEntity> source, LambdaExpression navigationPath) { _source = source; // 自定义逻辑:存储导航路径,用于后续中介处理 } public IEnumerator<TEntity> GetEnumerator() => _source.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }
核心原理说明
之前的问题根源是:自定义Include返回的IIncludableQueryable使用了具体集合类型(如List<B>)作为泛型参数,导致编译器尝试匹配非集合版本的ThenInclude(要求前序属性是单个实体,而非集合)。
修改后,List<B>/B[]会隐式转换为IEnumerable<B>,匹配集合版Include,返回的IIncludableQueryable<TEntity, IEnumerable<B>>会触发集合版ThenInclude,编译器能正确推断出前序元素是B,从而匹配o => o.Cs的导航属性。
内容的提问来源于stack exchange,提问作者George Ritchie
相关产品推荐
相关产品推荐

