C#中如何基于PropertyInfo构造Expression<Func<TEntity,object>>供EFCore的Include方法使用
现有代码的核心问题修正
你原来的逻辑存在两个本质错误,必须先修正:
property.GetValue(this)逻辑完全错误:this是仓储类实例,不是你要查询的实体对象,不可能拿到导航属性的运行时值,你应该判断导航属性的类型定义是否实现ISoftDelete,而非判断运行时值。- 反射
InvokeMember构造的表达式EF Core无法解析:EF Core的查询表达式必须是可翻译成SQL的强类型表达式,反射调用会直接触发运行时异常,必须用表达式树构造属性访问逻辑。
正确的表达式构造实现
EF Core 5及以上版本原生支持过滤的Include语法,我们直接通过表达式树动态拼接对应的查询逻辑即可,完整修正后的代码如下:
using System.Linq.Expressions; using System.Reflection; using Microsoft.EntityFrameworkCore; public interface ISoftDelete { public DateTime? Deleted { get; set;} } public class Repository<TEntity> { public DbContext Context { get; set; } // 缓存Where方法的定义,后面构造表达式用 private static readonly MethodInfo WhereMethod = typeof(Enumerable) .GetMethods(BindingFlags.Public | BindingFlags.Static) .First(m => m.Name == nameof(Enumerable.Where) && m.GetParameters().Length == 2); public IQueryable<TEntity> Include(string propertyName) { var entityType = typeof(TEntity); var property = entityType.GetProperty(propertyName); if (property == null) { throw new Exception($"属性名'{propertyName}'不存在"); } // 构造参数表达式:x => ... var parameter = Expression.Parameter(entityType, "x"); // 构造属性访问:x.PropertyName var propertyAccess = Expression.Property(parameter, property); Expression body = propertyAccess; var propertyType = property.PropertyType; // 情况1:判断是不是集合类型,且集合元素实现了ISoftDelete if ((propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || propertyType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))) { var elementType = propertyType.GetGenericArguments()[0]; if (typeof(ISoftDelete).IsAssignableFrom(elementType)) { // 构造元素的过滤条件:item => !item.Deleted.HasValue var itemParameter = Expression.Parameter(elementType, "item"); var deletedProperty = Expression.Property(itemParameter, nameof(ISoftDelete.Deleted)); var notDeleted = Expression.Not(Expression.Property(deletedProperty, nameof(Nullable<DateTime>.HasValue))); var filterLambda = Expression.Lambda(notDeleted, itemParameter); // 构造Where调用:x.PropertyName.Where(item => !item.Deleted.HasValue) var genericWhere = WhereMethod.MakeGenericMethod(elementType); body = Expression.Call(genericWhere, propertyAccess, filterLambda); } } // 情况2:单个引用类型实现了ISoftDelete else if (typeof(ISoftDelete).IsAssignableFrom(propertyType)) { // 注意:EF Core原生不支持对单个引用导航加Include过滤 // 这里我们返回正常属性访问,如果你需要过滤掉软删除的关联,有两种方案: // 1. 给关联实体全局配置软删除查询过滤器,Include时会自动过滤 // 2. 手动在当前返回的IQueryable后追加Where条件: // return Context.Set<TEntity>().Include(exp).Where(...) } // 构造最终的Lambda表达式,转成返回object的类型 var convertedBody = Expression.Convert(body, typeof(object)); Expression<Func<TEntity, object>> exp = Expression.Lambda<Func<TEntity, object>>(convertedBody, parameter); return Context.Set<TEntity>().Include(exp); } }
注意事项
- 过滤Include仅EF Core 5.0及以上版本支持,更低版本无法使用该特性。
- 单个引用导航的软删除过滤,建议直接给关联实体配置全局查询过滤器,EF Core会在所有查询(包括Include)中自动应用软删除条件,无需手动处理。
内容的提问来源于stack exchange,提问作者Jay
相关产品推荐
相关产品推荐

