PAYATHON 2026

如何在 C# 中通过参数更新 Oracle 的 VARRAY 类型列?

排障赵师傅

可以在 SQL 中调用 scrpt_app.HCODE_ARRAY 构造函数,为每个字符串分别绑定一个 OracleDbType.Varchar2 参数:

UPDATE mytable
SET hcodes = scrpt_app.HCODE_ARRAY(:h0, :h1, :h2)
WHERE ich = :ich

C# 传入普通字符串,Oracle 用这些字符串构造 HCODE_ARRAY 集合。这种写法无需在客户端配置 UDT 映射,也不依赖驱动直接绑定集合类型的能力。

第一种写法为什么报 ORA-00932

hcodes 列需要一个 HCODE_ARRAY 类型的值。原代码设置了 UdtTypeName,并将 string[] 赋给参数,但没有完成集合参数所需的类型声明和映射。

UdtTypeName 只指定 Oracle 用户定义类型的名称,不会自动将普通 C# 数组转换成该类型。报错中的 got CHAR 说明,这次绑定没有向数据库传入所需的集合值。调用 Prepare() 也无法解决类型不匹配的问题。

如果要保留直接绑定整个集合的 SQL:

UPDATE mytable SET hcodes = :hcodes WHERE ich = :ich

需要先确认驱动版本支持 UDT,再按驱动要求设置 OracleDbType.Array、完整的 UdtTypeName 和对应的集合类型映射。Oracle 21c 文档说明,托管 ODP.NET 从 21.2 起支持 UDT。Oracle.ManagedDataAccess.Client 能否直接绑定集合,取决于实际使用的驱动版本。Oracle UDT 与参数绑定文档

对于最多五个短字符串的场景,逐个绑定构造函数参数更直接。

第二种写法为什么报 ORA-22814

这行代码传入的是一个字符串参数:

cmd2.Parameters[":hcodes"].Value = "'E123','E456','E789'";

字符串中的单引号和逗号也是参数值的一部分。参数值不会被重新解析成 SQL,因此:

HCODE_ARRAY(:hcodes)

会尝试创建一个只有一个元素的集合,这个元素的完整内容是:

'E123','E456','E789'

它超过了元素类型 VARCHAR2(4) 的长度限制,因此报 ORA-22814。

集合构造函数的每个实参对应一个集合元素。传入三个元素时,需要提供三个参数,值分别为 "E123"、"E456"、"E789",无需额外添加 SQL 单引号。Oracle 类型构造表达式文档

C# 参数化更新示例

假设 ora 是已经打开的 OracleConnection,可以用以下代码更新三个元素:

using Oracle.ManagedDataAccess.Client;

using (OracleCommand cmd = ora.CreateCommand())
{
    cmd.BindByName = true;
    cmd.CommandText = @"
        UPDATE mytable
        SET hcodes = scrpt_app.HCODE_ARRAY(:h0, :h1, :h2)
        WHERE ich = :ich";

    cmd.Parameters.Add("h0", OracleDbType.Varchar2, 4).Value = "E123";
    cmd.Parameters.Add("h1", OracleDbType.Varchar2, 4).Value = "E456";
    cmd.Parameters.Add("h2", OracleDbType.Varchar2, 4).Value = "E789";
    cmd.Parameters.Add("ich", OracleDbType.Varchar2).Value = "xxx";

    int affectedRows = cmd.ExecuteNonQuery();
}

SQL 占位符带 :,添加参数时则使用不带 : 的名称。设置 BindByName = true 后,驱动会按名称匹配参数。Oracle BindByName 文档

元素数量不固定时怎么写

可以按数组长度生成占位符,再逐个绑定参数。下面的方法适用于示例中的 ASCII 编码值,允许传入零至五个元素:

using System;
using Oracle.ManagedDataAccess.Client;

public static int UpdateHcodes(
    OracleConnection ora,
    string ich,
    string[] codes)
{
    if (codes == null)
        throw new ArgumentNullException(nameof(codes));

    if (codes.Length > 5)
        throw new ArgumentException(
            "HCODE_ARRAY 最多允许 5 个元素。", nameof(codes));

    string[] placeholders = new string[codes.Length];

    using (OracleCommand cmd = ora.CreateCommand())
    {
        cmd.BindByName = true;

        for (int i = 0; i < codes.Length; i++)
        {
            // 此处按示例中的 ASCII 编码值校验长度。
            if (codes[i] == null || codes[i].Length > 4)
                throw new ArgumentException(
                    "每个编码必须非 null,且长度不能超过 4。",
                    nameof(codes));

            string name = "h" + i;
            placeholders[i] = ":" + name;

            cmd.Parameters.Add(
                name, OracleDbType.Varchar2, 4).Value = codes[i];
        }

        cmd.CommandText =
            "UPDATE mytable SET hcodes = scrpt_app.HCODE_ARRAY(" +
            string.Join(", ", placeholders) +
            ") WHERE ich = :ich";

        cmd.Parameters.Add("ich", OracleDbType.Varchar2).Value = ich;

        return cmd.ExecuteNonQuery();
    }
}

调用方式:

int affectedRows = UpdateHcodes(
    ora,
    "xxx",
    new[] { "E123", "E456", "E789" });

动态拼接的 SQL 片段只包含程序生成的参数名,编码内容和 ich 值仍通过参数传入。不要把实际字符串值拼接进 SQL。

使用时需要注意什么

  • VARRAY(5) 最多容纳五个元素,VARCHAR2(4) 限制每个元素的长度。增大 OracleParameter.Size 无法扩大数据库类型的容量,也不应依赖它校验输入长度。
  • 示例中的长度校验适用于 ASCII 编码。若允许中文等多字节字符,需要按数据库类型实际采用的 BYTE 或 CHAR 长度语义校验,不能直接把 C# 的 string.Length 当作数据库字节数。
  • 保留已确认可用的类型名 scrpt_app.HCODE_ARRAY。如果类型属于其他 schema,应使用实际的所有者名称,并确保当前用户有相应权限。
  • 传入空数组时,生成的调用是 scrpt_app.HCODE_ARRAY(),结果为空集合。这与将整列设为 SQL NULL 含义不同。Oracle 集合构造函数文档
  • ArrayBindCount 用于数组批量绑定,OracleCollectionType.PLSQLAssociativeArray 用于 PL/SQL 关联数组。两者都不能直接替代这里 SQL VARRAY 值所需的构造或映射。
  • 如果更新属于显式事务,调用方需要在成功后提交。ExecuteNonQuery() 返回的受影响行数可用于确认 WHERE 条件是否匹配到记录。

备注:内容仅供参考。