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

在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;

关键结论

  1. 参数类型不能用Raw:Raw仅适用于二进制数据,完全不匹配关联数组类型,应根据方案选择OracleDbType.RefCursor或OracleDbType.Array。
  2. 优先选择REF CURSOR方案:代码更简洁,EF Core支持更完善,无需复杂的自定义类型映射。
  3. 避免手动解析字符串:解析字符串容易出现格式错误,维护成本高,远不如类型映射可靠。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 09:31:02