.NET Core 7中EF FromSqlRaw结合动态表达式子查询转实体报错解决
EF Core 7 动态Select结合FromSqlRaw查询部分列报错问题
问题现象
在.NET Core 7的Entity Framework中,通过FromSqlRaw执行SQL查询后,使用表达式树实现动态Select转换为Entry实体类时,仅选择EntryId、Name部分列会抛出异常:
The required column 'Description' was not present in the results of a 'FromSql' operation.
但直接编写LINQ Select语句或查询所有列时可正常运行。
实体类定义
public class Entry { public int EntryId { get; set; } public string Name { get; set; } = null!; public string? Description { get; set; } }
DbContext定义
public DbSet<Entry> Entries { get; set; } = null!;
测试代码
public void RunTest(DbContext context) { // 直接LINQ查询正常 var list = context.Entries.Select(a => new Entry { EntryId = a.EntryId, Name = a.Name }).ToList(); var type = typeof(Entry); // 动态表达式查询部分列报错(原代码) var sqlQuery2 = "SELECT [EntryId], [Name] FROM [dbo].[Entry]"; var selectProps2 = new List<string> { "EntryId", "Name" }; var listExp2 = QueryTable(context, type, sqlQuery2, selectProps2).Cast<object>().ToList(); // 查询所有列正常 var sqlQueryAll = "SELECT [EntryId], [Name], [Description] FROM [dbo].[Entry]"; var selectPropsAll = new List<string> { "EntryId", "Name", "Description" }; var listExpAll = QueryTable(context, type, sqlQueryAll, selectPropsAll).Cast<object>().ToList(); }
原辅助方法(存在问题)
protected IEnumerable QueryTable(DbContext context, System.Type entityType, string sqlQuery, List<string> selectProps) { var parameter = Expression.Parameter(typeof(DbContext)); var expression = Expression.Call(parameter, "Set", new System.Type[] { entityType }); expression = Expression.Call(typeof(RelationalQueryableExtensions), "FromSqlRaw", new System.Type[] { entityType }, expression, Expression.Constant(sqlQuery), Expression.Constant(Array.Empty<object>())); expression = Select(entityType, expression, selectProps); var expressionResult = Expression.Lambda<Func<DbContext, IEnumerable>>(expression, parameter); var compiled = EF.CompileQuery(expressionResult); var result = compiled(context); return result; } protected static MethodCallExpression Select(System.Type entityType, Expression source, List<string> selectProps) { Dictionary<string, PropertyInfo> sourceProperties = selectProps.ToDictionary(name => name, name => entityType.GetProperty(name)!); var dynamicType = entityType; var expression = (MethodCallExpression)source; ParameterExpression parameter = Expression.Parameter(entityType); IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(parameter, sourceProperties[p.Name]))).OfType<MemberBinding>(); var constrType = dynamicType.GetConstructor(new System.Type[] { entityType }); if (constrType != null) { var constrTypeExp = Expression.New(constrType); Expression selector = Expression.Lambda(Expression.MemberInit(constrTypeExp, bindings), parameter); var typeArgs = new System.Type[] { entityType, dynamicType }; expression = Expression.Call(typeof(Queryable), "Select", typeArgs, expression, selector); } return expression; }
可正常运行的直接LINQ代码对比
var listRaw = context.Set<Entry>().FromSqlRaw(sqlQuery2).Select(a => new Entry { EntryId = a.EntryId, Name = a.Name }).ToList();
问题原因
- 错误使用字段而非属性:原代码中用
dynamicType.GetFields()获取成员,但Entry类的成员都是属性(Property),导致无法生成有效的属性绑定。 - 构造函数判断逻辑错误:代码试图寻找接收
Entry类型参数的构造函数,但Entry类不存在该构造函数,导致constrType为null,动态Select操作根本没有执行。EF Core直接尝试将FromSqlRaw结果映射到完整Entry实体,而SQL查询缺少Description列,因此抛出异常。
修复方案
修改后的辅助方法
protected IEnumerable QueryTable(DbContext context, System.Type entityType, string sqlQuery, List<string> selectProps) { var parameter = Expression.Parameter(typeof(DbContext)); var expression = Expression.Call(parameter, "Set", new[] { entityType }); // 调用FromSqlRaw expression = Expression.Call( typeof(RelationalQueryableExtensions), "FromSqlRaw", new[] { entityType }, expression, Expression.Constant(sqlQuery), Expression.Constant(Array.Empty<object>()) ); // 生成动态Select表达式 expression = Select(entityType, expression, selectProps); var lambda = Expression.Lambda<Func<DbContext, IEnumerable>>(expression, parameter); var compiled = EF.CompileQuery(lambda); return compiled(context); } protected static MethodCallExpression Select(System.Type entityType, Expression source, List<string> selectProps) { // 获取选中的属性信息 var selectedProperties = selectProps .Select(name => entityType.GetProperty(name)!) .Where(p => p != null) .ToList(); var parameter = Expression.Parameter(entityType, "x"); // 创建成员绑定:仅对选中的属性赋值 var bindings = selectedProperties .Select(p => Expression.Bind(p, Expression.Property(parameter, p))) .ToList(); // 使用无参构造函数初始化实体,并设置属性 var memberInit = Expression.MemberInit(Expression.New(entityType), bindings); var selectorLambda = Expression.Lambda(memberInit, parameter); // 调用Queryable.Select方法 return Expression.Call( typeof(Queryable), "Select", new[] { entityType, entityType }, source, selectorLambda ); }
修复要点
- 针对选中属性创建绑定:只处理传入的
selectProps中的属性,避免尝试映射未查询的列。 - 使用无参构造函数:直接调用
Entry的无参构造函数,通过MemberInit设置属性值,符合常规LINQ Select的写法。 - 确保Select操作执行:移除不必要的构造函数判断,保证动态Select被添加到查询链中,EF Core会先执行
FromSqlRaw获取部分列,再通过Select映射到实体的对应属性,不会要求查询所有列。
内容的提问来源于stack exchange,提问作者borisdj
相关产品推荐
相关产品推荐

