对象类型存储过程参数绑定无效问题求助
问题情况
用C#调用Oracle存储过程时,直接把DataTable赋值给UDT类型的参数,抛出了以下异常:
OracleInternal.ServiceObjects.OracleParameterImpl.SetUDTDataInBytes(Oracle.ManagedDataAccess.Client.OracleConnection,
OracleInternal.UDT.Types.UDTNamedType, object, out byte[], out object)
OracleInternal.ServiceObjects.OracleParameterImpl.SetUDTDataInBytes(Oracle.ManagedDataAccess.Client.OracleConnection,
OracleInternal.UDT.Types.UDTNamedType, object)
Oracle.ManagedDataAccess.Client.OracleParameter.PreBind_UDT(Oracle.ManagedDataAccess.Client.OracleConnection)
Oracle.ManagedDataAccess.Client.OracleParameter.PreBind(OracleInternal.ServiceObjects.OracleConnectionImpl,
OracleInternal.Common.ColumnDescribeInfo, ref bool, int, out
OracleInternal.Common.ColumnDescribeInfo, out object, bool,
OracleInternal.Common.SqlStatementType)
用到的C#代码如下:
public bool UpdateNumberInBulk_(DataTable dataTable) { try { using (OracleConnection connection = new OracleConnection(ConnectionString)) { connection.Open(); using (OracleCommand command = connection.CreateCommand()) { command.CommandText = "MY_PROCEDURE"; command.CommandType = CommandType.StoredProcedure; // 创建表参数对应的OracleParameter OracleParameter parameter = new OracleParameter(); parameter.ParameterName = "MY_ARRAY"; parameter.OracleDbType = OracleDbType.Object; parameter.Direction = ParameterDirection.Input; parameter.UdtTypeName = "my_type_t"; parameter.Value = dataTable; command.Parameters.Add(parameter); // 执行存储过程 command.ExecuteNonQuery(); } } return true; } catch (Exception ex) { throw; } }
问题根因
Oracle的Managed Data Access无法自动将DataTable映射为自定义UDT类型(my_type_t),直接赋值必然报错。UDT参数必须对应Oracle端定义的对象/集合类型,要么在.NET端实现类型映射,要么改用Oracle支持的参数传递方式。
解决办法
办法1:改用Oracle数组类型传递(适合集合类参数)
如果MY_ARRAY是Oracle的嵌套表/数组类型(非单个对象),可以将参数类型改为OracleDbType.Array,把DataTable转换为Oracle可识别的格式:
- 确认Oracle端的
my_type_t是集合类型,例如:TYPE my_type_t IS TABLE OF my_object_type - 修改参数绑定代码:
// 将DataTable转换为OracleDataReader using (OracleDataReader reader = dataTable.CreateDataReader()) { parameter.OracleDbType = OracleDbType.Array; parameter.UdtTypeName = "my_type_t"; // 建议使用全限定名,如SCHEMA.my_type_t parameter.Value = reader; }
办法2:实现UDT映射类(适合单个对象或自定义集合)
如果my_type_t是自定义对象类型,需要在C#中实现对应的映射类对接Oracle UDT:
- 定义与Oracle对象匹配的C#类:
[OracleCustomTypeMapping("my_type_t")] // 建议使用全限定名 public class MyType : IOracleCustomType, INullable { private bool _isNull; // 对应Oracle对象的字段,需与Oracle端定义一致 [OracleObjectMapping("COLUMN1")] public int Column1 { get; set; } [OracleObjectMapping("COLUMN2")] public string Column2 { get; set; } public bool IsNull => _isNull; public static MyType Null => new MyType { _isNull = true }; public void FromCustomObject(OracleConnection conn, IntPtr pUdt) { OracleUdt.SetValue(conn, pUdt, "COLUMN1", Column1); OracleUdt.SetValue(conn, pUdt, "COLUMN2", Column2); } public void ToCustomObject(OracleConnection conn, IntPtr pUdt) { Column1 = (int)OracleUdt.GetValue(conn, pUdt, "COLUMN1"); Column2 = (string)OracleUdt.GetValue(conn, pUdt, "COLUMN2"); } } // 配套的工厂类 public class MyTypeFactory : IOracleCustomTypeFactory { public IOracleCustomType CreateObject() { return new MyType(); } }
- 将DataTable的行转换为
MyType对象数组后赋值给参数:
List<MyType> myTypeList = new List<MyType>(); foreach (DataRow row in dataTable.Rows) { MyType obj = new MyType(); obj.Column1 = (int)row["Column1"]; // 对应DataTable列名 obj.Column2 = row["Column2"].ToString(); myTypeList.Add(obj); } parameter.OracleDbType = OracleDbType.Object; parameter.UdtTypeName = "my_type_t"; parameter.Value = myTypeList.ToArray();
办法3:检查UDT名称和权限
- 务必确保
UdtTypeName使用全限定名称(如用户名.my_type_t),避免因权限或命名空间问题导致类型无法识别。 - 确认执行代码的数据库用户拥有访问
my_type_t类型的权限。
办法4:换用OracleBulkCopy(批量更新场景适用)
如果只是批量更新数据,可直接用OracleBulkCopy将DataTable导入临时表,再调用存储过程处理临时表数据,绕开UDT绑定的复杂问题。
内容的提问来源于stack exchange,提问作者Tanweeruddin Beeharry

