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

EF Core 6存储过程通用DTO返回函数返回空对象问题及替代实现方案咨询

问题原因分析与解决方案

1. 空对象问题的核心原因

你遇到的Swagger返回空对象的问题,本质是JSON序列化规则不匹配:

  • 你的JobsDTO里定义的是公共字段(比如public string Luna;),但ASP.NET Core默认使用的System.Text.Json序列化器只会序列化带getter/setter的公共属性,完全忽略字段。所以即使res变量内存里有数据,序列化后输出的JSON就是空对象。
  • 另外你的泛型函数ExecuteProc<T>还有个隐性bug:FieldInfo[] fields = typeof(JobsDTO).GetFields();这里硬编码了JobsDTO的字段,而非泛型T的字段,虽然当前调用没问题,但换其他DTO会直接出错。

2. 快速修复当前代码

第一步:把DTO的字段改为属性

修改JobsDTO,将字段替换为标准的可序列化属性:

public class JobsDTO 
{ 
    public string Luna { get; set; } 
    public int NrSaptamana { get; set; } 
    public string ServiciuPrestat { get; set; } 
    public bool IsServiciuPrestat { get; set; } 
}

第二步:修复泛型函数的硬编码问题

把反射目标从JobsDTO改为泛型T,同时适配属性映射:

public async Task<List<T>> ExecuteProc<T>() where T : new() 
{ 
    DataTable dataTable = new DataTable(); 
    var connection = databaseContext.Database.GetDbConnection(); 
    var dbFactory = DbProviderFactories.GetFactory(connection); 
    using(var cmd = dbFactory!.CreateCommand()) 
    { 
        cmd.Connection = connection; 
        cmd.CommandType = System.Data.CommandType.StoredProcedure; 
        cmd.CommandText = "GetJob"; 
        using(var adapter = dbFactory.CreateDataAdapter()) 
        { 
            adapter.SelectCommand = cmd; 
            adapter.Fill(dataTable); 
        } 
    } 

    List<T> list = new List<T>(); 
    // 改为获取泛型T的属性
    PropertyInfo[] props = typeof(T).GetProperties(); 
    foreach (DataRow dr in dataTable.Rows) 
    { 
        T t = new T(); 
        foreach (PropertyInfo prop in props) 
        {
            // 先判断列是否存在,避免列名不匹配报错
            if(dataTable.Columns.Contains(prop.Name))
            {
                // 处理数据库NULL值
                var value = dr[prop.Name] == DBNull.Value ? null : dr[prop.Name];
                prop.SetValue(t, value);
            }
        }
        list.Add(t); 
    } 
    return list; 
}

3. EF Core 6中调用存储过程返回DTO的其他可行方案

方案1:EF Core原生无键实体映射

EF Core 6支持将无键实体直接映射到存储过程结果,无需手动反射:

  1. 在DbContext中注册无键实体:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // 标记JobsDTO为无键实体
    modelBuilder.Entity<JobsDTO>().HasNoKey();
}
  1. 直接调用存储过程:
public async Task<List<JobsDTO>> GetJobs()
{
    return await databaseContext.Set<JobsDTO>()
        .FromSqlRaw("EXEC GetJob")
        .ToListAsync();
}

方案2:使用Dapper(轻量ORM,与EF Core兼容)

Dapper是专门为存储过程/原生SQL设计的轻量ORM,代码简洁且性能优异:

  1. 先安装NuGet包:Install-Package Dapper
  2. 实现调用:
public async Task<List<JobsDTO>> ExecuteProcWithDapper()
{
    using var connection = databaseContext.Database.GetDbConnection();
    return (await connection.QueryAsync<JobsDTO>(
        "GetJob", 
        commandType: CommandType.StoredProcedure
    )).ToList();
}

Dapper会自动处理DataReader到DTO的映射,支持字段/属性两种定义方式。

方案3:使用SqlDataReader手动映射(更高效)

如果不想引入第三方库,用SqlDataReader替代DataTable可以节省内存开销:

public async Task<List<T>> ExecuteProc<T>() where T : new()
{
    var list = new List<T>();
    var props = typeof(T).GetProperties();
    
    using var connection = databaseContext.Database.GetDbConnection();
    await connection.OpenAsync();
    
    using var cmd = connection.CreateCommand();
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.CommandText = "GetJob";
    
    using var reader = await cmd.ExecuteReaderAsync();
    while (await reader.ReadAsync())
    {
        T t = new T();
        foreach (var prop in props)
        {
            var ordinal = reader.GetOrdinal(prop.Name);
            if (!reader.IsDBNull(ordinal))
            {
                prop.SetValue(t, reader.GetValue(ordinal));
            }
        }
        list.Add(t);
    }
    return list;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 12:57:39