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

.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();

问题原因

  1. 错误使用字段而非属性:原代码中用dynamicType.GetFields()获取成员,但Entry类的成员都是属性(Property),导致无法生成有效的属性绑定。
  2. 构造函数判断逻辑错误:代码试图寻找接收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
    );
}

修复要点

  1. 针对选中属性创建绑定:只处理传入的selectProps中的属性,避免尝试映射未查询的列。
  2. 使用无参构造函数:直接调用Entry的无参构造函数,通过MemberInit设置属性值,符合常规LINQ Select的写法。
  3. 确保Select操作执行:移除不必要的构造函数判断,保证动态Select被添加到查询链中,EF Core会先执行FromSqlRaw获取部分列,再通过Select映射到实体的对应属性,不会要求查询所有列。

内容的提问来源于stack exchange,提问作者borisdj

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 21:13:09