EF Core 8预览版调用存储过程:仅返回部分列报错的解决诉求
问题场景
在.NET 6 API中使用EF Core 8预览版通过原生SQL调用存储过程时,若存储过程返回的列包含模型所有带公共setter的属性,程序运行正常;但如果仅返回部分列却复用同一模型,会抛出错误:
The required column 'colname' was not present in the results of a 'FromSql' operation.
当前调用存储过程的实现代码如下:
public IEnumerable<T> ExecStoredProc<T>(string procName, IDictionary<string, object> parameters) { try { var inputParameter = new List<SqlParameter>(parameters.Count); foreach (var parameter in parameters) { inputParameter.Add(new SqlParameter { ParameterName = parameter.Key, Value = parameter.Value ?? DBNull.Value }); } string procWithParameter = string.Format("exec {0} {1}", procName, string.Join(", ", inputParameter.Select(p => p.ParameterName))); var result = _dbContext.Database.SqlQueryRaw<T>(procWithParameter, inputParameter.ToArray()); return result.ToList(); } catch (System.Exception ex) { throw ex; } }
解决方案
方案1:将可选属性设为可空类型
把模型中允许缺失的属性改为可空值类型(值类型加?,引用类型默认支持空值),EF Core会自动忽略缺失的列,并将对应属性设为null。
示例模型修改:
public class YourModel { // 必须返回的列,保持非可空 public int Id { get; set; } // 允许缺失的列,改为可空类型 public string? Description { get; set; } public int? Age { get; set; } }
方案2:使用无实体类型(EF Core 7+支持)
定义一个仅包含存储过程返回列的无实体类,规避原模型的字段约束。
- 创建无实体模型类:
public class PartialProcResult { public int Id { get; set; } public string Name { get; set; } // 仅包含存储过程实际返回的列 }
- 在DbContext中配置该类为无实体类型:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<PartialProcResult>().HasNoKey(); }
- 调用存储过程时指定该类型:
var result = _dbContext.Database.SqlQueryRaw<PartialProcResult>(procWithParameter, inputParameter.ToArray());
方案3:手动读取DataReader映射结果
直接通过DbDataReader读取数据,手动映射到目标模型,跳过缺失的列。修改原方法如下:
public IEnumerable<T> ExecStoredProc<T>(string procName, IDictionary<string, object> parameters) where T : new() { try { var inputParameter = new List<SqlParameter>(parameters.Count); foreach (var parameter in parameters) { inputParameter.Add(new SqlParameter { ParameterName = parameter.Key, Value = parameter.Value ?? DBNull.Value }); } using var command = _dbContext.Database.GetDbConnection().CreateCommand(); command.CommandText = $"exec {procName} {string.Join(", ", inputParameter.Select(p => p.ParameterName))}"; command.Parameters.AddRange(inputParameter.ToArray()); _dbContext.Database.OpenConnection(); using var reader = command.ExecuteReader(); var results = new List<T>(); var writableProps = typeof(T).GetProperties().Where(p => p.CanWrite).ToList(); while (reader.Read()) { var item = new T(); foreach (var prop in writableProps) { try { var ordinal = reader.GetOrdinal(prop.Name); if (!reader.IsDBNull(ordinal)) { var value = reader.GetValue(ordinal); prop.SetValue(item, value); } } catch (IndexOutOfRangeException) { // 列缺失时跳过,保持属性默认值 continue; } } results.Add(item); } return results; } catch (Exception ex) { throw ex; } finally { if (_dbContext.Database.GetDbConnection().State == System.Data.ConnectionState.Open) { _dbContext.Database.CloseConnection(); } } }
该方法通过反射获取可写属性,遍历DataReader时跳过不存在的列,无需修改原模型结构。
内容的提问来源于stack exchange,提问作者Rajkumar Ks
相关产品推荐
相关产品推荐

