如何获取Entity Framework查询中包含的DbSet属性名称?
问题描述
我的DbContext类定义了以下DbSet属性:
public virtual DbSet<Employee> Employees { get; set; } public virtual DbSet<Company> Companies { get; set; } public virtual DbSet<Product> Products { get; set; }
实体关系为:每个Employee隶属于一个Company,每个Company拥有若干Product。基于此编写的EF查询示例如下:
IQueryable<Employee> query1 = context.Employees.Where(e => e.Id == 1); IQueryable<Employee> query2 = context.Employees.Where(e => e.Id == 1).Include(x => x.Company); IQueryable<Employee> query3 = context.Employees.Where(e => e.Id == 1).Include(x => x.Company).ThenInclude(x => x.Products);
我需要获取给定查询中包含的DbSet属性名称列表,比如query2应输出Employees和Companies。
我尝试过检查查询的Expression属性并转换为MethodCallExpression,再查看Arguments中的导航属性表达式,但只能通过导航属性的类型推断DbSet名称(比如Company对应Companies),这种方式不够可靠。
请问有没有更可靠的方法获取EF查询中包含的DbSet属性名称?
解决方案
可以借助Entity Framework的元数据模型(Model)实现,这是最可靠的方式——元数据直接映射了实体类型与DbSet属性的关联关系,完全避免手动推断的误差。具体步骤如下:
- 从查询表达式树中提取根实体类型(即初始DbSet对应的实体)
- 遍历查询中的
Include/ThenInclude链,解析每个导航属性对应的目标实体类型 - 通过DbContext的Model匹配实体类型对应的DbSet名称
代码示例(EF Core)
using Microsoft.EntityFrameworkCore; using System.Linq.Expressions; using System.Reflection; public static List<string> GetInvolvedDbSetNames(IQueryable query, DbContext context) { var dbSetNames = new HashSet<string>(); // 提取查询根对应的实体类型及DbSet名称 var rootEntityType = GetRootEntityType(query.Expression); if (rootEntityType != null) { dbSetNames.Add(context.Model.FindEntityType(rootEntityType).GetDbSetName()); } // 解析所有Include/ThenInclude的导航属性,获取对应DbSet名称 var includeExpressions = ExtractIncludeExpressions(query.Expression); foreach (var includeExpr in includeExpressions) { var targetEntityType = GetTargetEntityTypeFromNavigation(includeExpr, context.Model); if (targetEntityType != null) { dbSetNames.Add(targetEntityType.GetDbSetName()); } } return dbSetNames.ToList(); } // 从表达式树中提取查询根的实体类型 private static Type GetRootEntityType(Expression expression) { while (expression is MethodCallExpression methodCall) { // 跳过Where/Include/ThenInclude等链式调用,找到初始DbSet访问节点 if (methodCall.Method.Name == "Where" || methodCall.Method.Name.StartsWith("Include") || methodCall.Method.Name.StartsWith("ThenInclude")) { expression = methodCall.Arguments[0]; } else { break; } } // 识别DbSet属性并提取泛型参数(实体类型) if (expression is MemberExpression memberExpr && memberExpr.Member is PropertyInfo prop && prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)) { return prop.PropertyType.GetGenericArguments()[0]; } return null; } // 提取所有Include/ThenInclude中的导航表达式 private static List<Expression> ExtractIncludeExpressions(Expression expression) { var includes = new List<Expression>(); while (expression is MethodCallExpression methodCall) { if (methodCall.Method.Name.StartsWith("Include") || methodCall.Method.Name.StartsWith("ThenInclude")) { // 处理导航表达式的包装(UnaryExpression) var navigationExpr = methodCall.Arguments[1]; if (navigationExpr is UnaryExpression unary) { navigationExpr = unary.Operand; } includes.Add(navigationExpr); } expression = methodCall.Arguments[0]; } return includes; } // 从导航表达式获取目标实体类型 private static IEntityType GetTargetEntityTypeFromNavigation(Expression navigationExpr, IModel model) { if (navigationExpr is LambdaExpression lambda) { var memberExpr = lambda.Body as MemberExpression; if (memberExpr != null) { var sourceEntityType = model.FindEntityType(lambda.Parameters[0].Type); var navigation = sourceEntityType.FindNavigation(memberExpr.Member.Name); return navigation?.TargetEntityType; } } return null; }
适配说明
如果使用EF6,需要调整元数据相关API:
- 用
context.Model.GetEntityTypes()替代context.Model.FindEntityType() - 通过反射遍历DbContext的属性,找到对应实体类型的DbSet属性名称
内容的提问来源于stack exchange,提问作者IvanH
相关产品推荐
相关产品推荐

