在EF Core中处理Oracle存储包的自定义记录表输出参数问题
问题解决方案
针对你遇到的Oracle存储过程输出关联数组在EF Core中处理的问题,以下是具体的解决方案和最佳实践:
核心问题分析
你当前将o_tijd参数设为Raw类型是错误的——Raw用于二进制数据,完全不匹配PL/SQL中INDEX BY BINARY_INTEGER的RECORD表类型(关联数组)。下面提供两种可行方案,优先推荐第一种(更简洁易维护)。
方案一:修改存储过程返回REF CURSOR(优先选择)
如果允许修改Oracle存储包,将输出参数改为REF CURSOR是最适配EF Core的方式,因为EF对游标类型的支持更成熟,代码逻辑更直观。
修改后的Oracle存储包
create or replace PACKAGE Pkg_Punt_Tijd AS TYPE recTijd IS RECORD ( TijdVan DATE, TijdTot DATE, Prioriteit NUMBER(1), Percentage NUMBER); -- 将原关联数组改为REF CURSOR类型 TYPE curTijd IS REF CURSOR RETURN recTijd; PROCEDURE sp_get_punt_tijd( i_PuntTijdVan IN DATE, i_PuntTijdTot IN DATE, i_ProjectNr IN VARCHAR2, i_DagType IN VARCHAR2, o_tijd OUT curTijd, o_recnum OUT NUMBER ); END;
C# EF Core调用代码
首先创建对应RECORD的实体类:
public class RecTijd { public DateTime TijdVan { get; set; } public DateTime TijdTot { get; set; } public int Prioriteit { get; set; } public decimal Percentage { get; set; } }
然后编写调用逻辑:
// 定义输入参数 var p0 = new OracleParameter("i_PuntTijdVan", OracleDbType.Date, startDateTime, ParameterDirection.Input); var p1 = new OracleParameter("i_PuntTijdTot", OracleDbType.Date, endDateTime, ParameterDirection.Input); var p2 = new OracleParameter("i_ProjectNr", OracleDbType.Varchar2, projectNumber, ParameterDirection.Input); var p3 = new OracleParameter("i_DagType", OracleDbType.Varchar2, dayType, ParameterDirection.Input); // 定义输出参数:游标和记录数 var outputCursor = new OracleParameter("o_tijd", OracleDbType.RefCursor, ParameterDirection.Output); var outputRecnum = new OracleParameter("o_recnum", OracleDbType.Decimal, ParameterDirection.Output); // 执行存储过程 var command = "BEGIN Pkg_Punt_Tijd.SP_GET_PUNT_TIJD(:i_PuntTijdVan, :i_PuntTijdTot, :i_ProjectNr, :i_DagType, :o_tijd, :o_recnum); END;"; await _context.Database.ExecuteSqlRawAsync(command, p0, p1, p2, p3, outputCursor, outputRecnum); // 读取游标数据并映射到实体类 using var reader = ((OracleRefCursor)outputCursor.Value).GetDataReader(); var resultList = new List<RecTijd>(); while (reader.Read()) { resultList.Add(new RecTijd { TijdVan = reader.GetDateTime(0), TijdTot = reader.GetDateTime(1), Prioriteit = reader.GetInt32(2), Percentage = reader.GetDecimal(3) }); } // 获取记录数 var recnum = (decimal)outputRecnum.Value;
方案二:不修改存储过程,处理PL/SQL关联数组
如果无法修改存储包,需要通过ODP.NET的自定义类型映射来处理关联数组,避免手动解析字符串(易出错、维护性差)。
步骤1:创建C#自定义类型映射类
实现ODP.NET的自定义类型接口,映射PL/SQL中的RECORD和关联数组:
// 映射PL/SQL的recTijd记录类型 [OracleCustomTypeMapping("Pkg_Punt_Tijd.recTijd")] public class RecTijd : IOracleCustomType, INullable { [OracleObjectMappingAttribute("TIJDVAN")] public DateTime? TijdVan { get; set; } [OracleObjectMappingAttribute("TIJDOT")] public DateTime? TijdTot { get; set; } [OracleObjectMappingAttribute("PRIORITEIT")] public int? Prioriteit { get; set; } [OracleObjectMappingAttribute("PERCENTAGE")] public decimal? Percentage { get; set; } private bool _isNull; public bool IsNull => _isNull; public static RecTijd Null => new RecTijd { _isNull = true }; public void FromCustomObject(OracleConnection con, IntPtr pUdt) { OracleUdt.SetValue(con, pUdt, "TIJDVAN", TijdVan); OracleUdt.SetValue(con, pUdt, "TIJDOT", TijdTot); OracleUdt.SetValue(con, pUdt, "PRIORITEIT", Prioriteit); OracleUdt.SetValue(con, pUdt, "PERCENTAGE", Percentage); } public void ToCustomObject(OracleConnection con, IntPtr pUdt) { TijdVan = (DateTime?)OracleUdt.GetValue(con, pUdt, "TIJDVAN"); TijdTot = (DateTime?)OracleUdt.GetValue(con, pUdt, "TIJDOT"); Prioriteit = (int?)OracleUdt.GetValue(con, pUdt, "PRIORITEIT"); Percentage = (decimal?)OracleUdt.GetValue(con, pUdt, "PERCENTAGE"); } } // 映射PL/SQL的curTijd关联数组类型 [OracleCustomTypeMapping("Pkg_Punt_Tijd.curTijd")] public class CurTijdFactory : IOracleCustomTypeFactory, IOracleArrayTypeFactory { public IOracleCustomType CreateObject() { return new RecTijd(); } public Array CreateArray(int numElems) { return new RecTijd[numElems]; } public Array CreateStatusArray(int numElems) { return null; } }
步骤2:EF Core调用代码
// 定义输入参数 var p0 = new OracleParameter("i_PuntTijdVan", OracleDbType.Date, startDateTime, ParameterDirection.Input); var p1 = new OracleParameter("i_PuntTijdTot", OracleDbType.Date, endDateTime, ParameterDirection.Input); var p2 = new OracleParameter("i_ProjectNr", OracleDbType.Varchar2, projectNumber, ParameterDirection.Input); var p3 = new OracleParameter("i_DagType", OracleDbType.Varchar2, dayType, ParameterDirection.Input); // 定义输出关联数组参数,指定UDT类型名 var outputTijd = new OracleParameter("o_tijd", OracleDbType.Array, ParameterDirection.Output); outputTijd.UdtTypeName = "Pkg_Punt_Tijd.curTijd"; var outputRecnum = new OracleParameter("o_recnum", OracleDbType.Decimal, ParameterDirection.Output); // 执行存储过程 var command = "BEGIN Pkg_Punt_Tijd.SP_GET_PUNT_TIJD(:i_PuntTijdVan, :i_PuntTijdTot, :i_ProjectNr, :i_DagType, :o_tijd, :o_recnum); END;"; await _context.Database.ExecuteSqlRawAsync(command, p0, p1, p2, p3, outputTijd, outputRecnum); // 获取返回的关联数组数据 var resultArray = (RecTijd[])outputTijd.Value; var recnum = (decimal)outputRecnum.Value;
关键结论
- 参数类型不能用Raw:Raw仅适用于二进制数据,完全不匹配关联数组类型,应根据方案选择
OracleDbType.RefCursor或OracleDbType.Array。 - 优先选择REF CURSOR方案:代码更简洁,EF Core支持更完善,无需复杂的自定义类型映射。
- 避免手动解析字符串:解析字符串容易出现格式错误,维护成本高,远不如类型映射可靠。
内容的提问来源于stack exchange,提问作者Abdelrahman Hazem
相关产品推荐
相关产品推荐

