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

C#中如何基于PropertyInfo构造Expression<Func<TEntity,object>>供EFCore的Include方法使用

现有代码的核心问题修正

你原来的逻辑存在两个本质错误,必须先修正:

  1. property.GetValue(this)逻辑完全错误:this是仓储类实例,不是你要查询的实体对象,不可能拿到导航属性的运行时值,你应该判断导航属性的类型定义是否实现ISoftDelete,而非判断运行时值。
  2. 反射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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 06:36:05