如何在 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(),结果为空集合。这与将整列设为 SQLNULL含义不同。Oracle 集合构造函数文档 ArrayBindCount用于数组批量绑定,OracleCollectionType.PLSQLAssociativeArray用于 PL/SQL 关联数组。两者都不能直接替代这里 SQLVARRAY值所需的构造或映射。- 如果更新属于显式事务,调用方需要在成功后提交。
ExecuteNonQuery()返回的受影响行数可用于确认WHERE条件是否匹配到记录。
备注:内容仅供参考。
