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支持将无键实体直接映射到存储过程结果,无需手动反射:
- 在DbContext中注册无键实体:
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 标记JobsDTO为无键实体 modelBuilder.Entity<JobsDTO>().HasNoKey(); }
- 直接调用存储过程:
public async Task<List<JobsDTO>> GetJobs() { return await databaseContext.Set<JobsDTO>() .FromSqlRaw("EXEC GetJob") .ToListAsync(); }
方案2:使用Dapper(轻量ORM,与EF Core兼容)
Dapper是专门为存储过程/原生SQL设计的轻量ORM,代码简洁且性能优异:
- 先安装NuGet包:
Install-Package Dapper - 实现调用:
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
相关产品推荐
相关产品推荐

