游乐游手机版
首页/数据库/文章详情

.NET如何调用带复杂参数的Oracle存储过程_自定义类型

时间:2026-04-17 18:20
Oracle UDT 与 Table Type 使用指南:数据库预创建与 ODP NET 参数配置详解 Oracle UDT 和 Table Type 必须在数据库中预先创建 在 C 应用程序中调用 Oracle 存储过程时,如果涉及自定义对象类型(UDT)或表类型(Table Type),必须确

Oracle UDT 与 Table Type 使用指南:数据库预创建与 ODP.NET 参数配置详解

Oracle UDT 和 Table Type 必须在数据库中预先创建

在 C# 应用程序中调用 Oracle 存储过程时,如果涉及自定义对象类型(UDT)或表类型(Table Type),必须确保这些类型已在 Oracle 数据库中成功创建并处于有效状态。若数据库端类型定义缺失或无效,即使 C# 代码参数配置正确,系统仍会抛出 ORA-04043: object ... does not existORA-00902: invalid datatype 等错误。问题的核心在于数据库对象不存在,而非代码逻辑错误。

.NET如何调用带复杂参数的Oracle存储过程_自定义类型

因此,在编写 C# 调用代码之前,务必在 Oracle 数据库中完成以下验证:

  • 确认对象类型(OBJECT)已通过 CREATE OR REPLACE TYPE ... AS OBJECT 语句成功创建,且状态为 VALID
  • 确认表类型(TABLE OF ...)已通过 CREATE OR REPLACE TYPE ... AS TABLE OF ... 语句创建,并且其依赖的基础对象类型已存在。

常见错误场景是:先创建了 student_table_type,却未创建其依赖的 student_obj_type。这种情况下,程序可能在连接阶段不报错,但在执行存储过程时,会因类型解析失败而提示“类型不存在”。

ODP.NET 中传递 Table Type 需使用 OracleCollectionType.PLSQLAssociativeArray

在 .NET 中调用 Oracle 存储过程并传递表类型参数时,必须使用 Oracle 官方的 ODP.NET 数据提供程序(即 Oracle.ManagedDataAccess 库)。已过时的 System.Data.OracleClient 无法识别 PL/SQL 关联数组,不应继续使用。

配置 ODP.NET 参数时,需重点关注以下三个要点:

  • 必须将 OracleParameter.CollectionType 属性设置为 OracleCollectionType.PLSQLAssociativeArray,而非 PLSQLNestedTable 或其他选项。
  • OracleParameter.Value 必须赋值为 .NET 数组(如 object[] 或具体类型的数组),直接传入 ListIEnumerable 集合将导致错误。
  • 存储过程中对应的输入参数必须声明为 IN my_table_type 形式,且 my_table_type 必须是数据库中已定义的 TABLE OF ... 类型。

以下是一个配置示例:

var students = new object[] {
    new object[] { 1, "Alice", DateTime.Now },
    new object[] { 2, "Bob", DateTime.Now }
};
var param = new OracleParameter("p_students", OracleDbType.Object);
param.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
param.UdtTypeName = "STUDENT_TABLE_TYPE"; // 注意:类型名需全大写,严格匹配数据库定义
param.Value = students;

UDT 对象数组需映射至 .NET 类并注册 OracleUdtMapping

若要传递自定义对象类型(如 TestObjType),而非简单字段组合,则必须为 ODP.NET 建立 .NET 类与 Oracle 对象类型之间的映射关系。仅在 C# 中定义类是不够的,必须进行显式的映射注册,否则无法正确序列化与反序列化数据。

映射注册主要有两种方式:

  • 全局注册(推荐):在应用程序启动时(如 Main 方法或 Global.asax 中)调用 OracleUdtMapping.Add 方法,一次性完成所有自定义类型的映射注册。
  • 局部注册:在每次创建数据库连接前手动注册映射。此方式易遗漏,增加维护复杂度,一般不推荐。

注册代码示例如下(需确保命名空间、属性顺序及大小写与数据库定义完全一致):

[OracleCustomTypeMapping("SCHEMA.TESTOBJTYPE")]
public class TestObjType : IOracleCustomType
{
    public decimal? ID { get; set; }
    public string Name { get; set; }
    public DateTime? CreateTime { get; set; }
    public string Status { get; set; }
    public string Description { get; set; }

    public void FromCustomObject(OracleConnection con, IntPtr pUdt)
    {
        // 实现字段写入逻辑
    }
    public void ToCustomObject(OracleConnection con, IntPtr pUdt)
    {
        // 实现字段读取逻辑
    }
}

// 在应用启动时注册映射:
OracleUdtMapping.Add(new OracleUdtMapping{
    UdtTypeName = "SCHEMA.TESTOBJTYPE",
    CSharpTypeName = typeof(TestObjType).AssemblyQualifiedName
});

若遗漏 [OracleCustomTypeMapping] 特性,或注册时类型名大小写不匹配(如数据库定义为 TESTOBJTYPE,代码中误写为 TestObjType),可能导致 ORA-03113: end-of-file on communication channel 错误或静默失败。

输出参数为 UDT 时,OracleParameter.Direction 须设为 Output 且不预设 Value

当存储过程返回 TABLE OF ... 类型的结果集时,输出参数的配置是常见错误点。若为输出参数预先赋值(如 param.Value = new object[0]),ODP.NET 会尝试向 Oracle 写入该空数组,从而引发类型不匹配异常。

正确的配置步骤如下:

  • Direction 属性设置为 ParameterDirection.Output
  • 保持 Value 属性为空(不进行赋值)
  • 命令执行完毕后,从 param.Value 属性中读取返回的 OracleUdtobject[] 数组

假设存储过程定义如下:

PROCEDURE get_students(p_out OUT student_table_type)

则 C# 端应如下配置:

var outParam = new OracleParameter("p_out", OracleDbType.Object);
outParam.Direction = ParameterDirection.Output;
outParam.UdtTypeName = "STUDENT_TABLE_TYPE";
outParam.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
cmd.Parameters.Add(outParam);

cmd.ExecuteNonQuery();

// 执行完成后读取结果:
var resultArray = (object[])outParam.Value;

需注意一个细节:UDT 输出结果中的每个元素默认是 OracleUdt 实例,不会自动转换为已定义的 .NET 类——除非已实现并注册完整的序列化逻辑。若直接进行强制类型转换,将抛出 InvalidCastException 异常。

来源:https://www.php.cn/faq/2346412.html
上一篇ddl是什么意思 入门:从基础认知到上手使用 下一篇select top 入门:从基础认知到上手使用
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Redis 7.0增量AOF重写RDB前导码配置详解
数据库 · 2026-07-02

Redis 7.0增量AOF重写RDB前导码配置详解

先说一个几乎所有人都踩过的典型误区:很多人把 aof-use-rdb-preamble yes 当作开启“增量重写”的开关。实际上,这个配置只干了一件事——让重写后的 AOF 文件头部带上 RDB 快照。它解决的是加载速度问题,跟“增量重写”本身的概念压根不是一回事。真正的增量重写,依赖的是 Red

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践
数据库 · 2026-07-02

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践

直接在Tornado里用SQLAlchemy同步执行SQL,结果就是阻塞IOLoop,所谓“异步框架里写同步数据库代码”,等于白搭。安全执行的关键不是“怎么写SQL”,而是“怎么不卡住事件循环”。 为什么不能在RequestHandler里直接调用session execute() 因为sessio

利用SQL触发器实现在INSERT数据时自动同步到审计表
数据库 · 2026-07-02

利用SQL触发器实现在INSERT数据时自动同步到审计表

先说结论:可以用触发器把 INSERT 数据同步到审计表,但必须用 AFTER INSERT,并且审计表的字段顺序、类型、字符集得和源表严格一致。否则,轻则写入错位、数据截断,重则直接报错、丢数据。下面把这些坑一个一个掰开说。 能,但必须用 AFTER INSERT,且审计表字段顺序、类型、字符集要

如何用SQL编写按不同工作日统计员工出勤率
数据库 · 2026-07-02

如何用SQL编写按不同工作日统计员工出勤率

在实际业务中,统计不同工作日的出勤率是HR系统里的高频需求。如果直接按日期函数分组,很容易掉进语言环境、索引失效或分母口径的坑里。下面就来拆解具体的实现要点。 必须用 CASE WHEN 将日期映射为固定 weekday 标签(如 Mon )再分组,避免语言环境导致的分组断裂;需过滤 DOW IN

Spring Boot 3动态拼接SQL为何引发严重安全漏洞
数据库 · 2026-07-02

Spring Boot 3动态拼接SQL为何引发严重安全漏洞

SQL注入漏洞的核心成因,本质上是因为用户输入直接参与了SQL语句的字符串拼接,而未采用参数化绑定机制。在MyBatis中使用${}、QueryWrapper中调用apply()与last()、JPA的@Query注解进行拼接等操作,都会绕过PreparedStatement的安全防护。动态字段必须