【发布时间】:2015-09-11 23:10:38
【问题描述】:
关于 UDT(用户定义类型)行为,我在使用 Odp.net 时遇到了一些问题。
当我有一个包含返回特定 UDT 的 OUT 参数的过程时,我的问题就出现了。
当我为 OUT 参数返回 一个实例化的 UDT 时,没问题。
当我为 OUT 参数返回 NULL 值时我收到 NullReference 错误:
“System.NullReferenceException”类型的第一次机会异常 发生在 Oracle.DataAccess.dll
我尝试在相关的 OracleCommand 参数上设置 IsNullable = True,但没有成功。
我可以毫无问题地发送和接收相当复杂的 UDT,例如具有嵌套 UDT 和对象集合的 UDT 以及具有嵌套对象集合的 UDT。
除了使 Oracle 过程返回对象类型的实例之外,是否可以使用 ODP.NET 解决此问题?
更新 - 已解决:
问题在于具有嵌套 UDT 类型的 UDT 类型未正确初始化为 null。使用自动生成的代码解决了这个问题。Using Oracle User-Defined Types with .NET and Visual Studio
Christian Shay,感谢您解决问题 - 自动生成的代码 可能是比实现基类来处理更好的选择 大多数行为,尽管这是可能的。
Oracle 存储过程签名是:
PROCEDURE CREATE_DEFINITIONS_FOR_GROUP(
P_GRP_NO IN NUMBER
,P_DATE IN DATE
,P_ERROR_CODE OUT MYSCHEMA.ERROR_CODE);
打开连接后,我使用 ODP.NET 在 C# 中调用此代码:
using (var oCmd = new OracleCommand
{
CommandText = "MYSCHEMA.MYPACKAGE.CREATE_DEFINITIONS_FOR_GROUP",
Connection = oConn,
CommandType = CommandType.StoredProcedure
})
{
try
{
oCmd.Parameters.Add(OracleParameterFactory.CreateInParam(
"P_GRP_NO", OracleDbType.Int64, value: groupNo));
oCmd.Parameters.Add(OracleParameterFactory.CreateInParam(
"P_DATE", OracleDbType.Date, value: dateOfGroup));
oCmd.Parameters.Add(OracleParameterFactory.CreateOutParamForUdtType(
"P_ERROR_CODE", "MYSCHEMA.ERROR_CODE"));
oCmd.ExecuteNonQuery();
var report = oCmd.Parameters["P_ERROR_CODE"].Value as DbErrorCode;
return report;
}
finally
{
CommandHelpers.DisposeParameters(oCmd);
}
}
UDT 类型在 .NET 中被定义为有效的 UDT 类型,如下所示:
public class DbErrorCode : TypeTemplate
{
[OracleObjectMapping("ERROR_CODE")]
public decimal Code { get; set; }
[OracleObjectMapping("DESCRIPTION")]
public string Description { get; set; }
}
基本的 TypeTemplate 类定义如下:
public class TypeTemplate : IOracleCustomType, INullable
{
public virtual void FromCustomObject(OracleConnection con, IntPtr pUdt)
{
foreach (var p in GetType().GetProperties())
{
// Must ignore these two properties
if (p.Name == "Null" || p.Name == "IsNull") continue;
var oracleObjectMappingAttribute = p.GetCustomAttributes(typeof(OracleObjectMappingAttribute), false)[0] as OracleObjectMappingAttribute;
if (oracleObjectMappingAttribute == null) continue;
var attributeName = oracleObjectMappingAttribute.AttributeName;
if (p.GetCustomAttributes(typeof(IgnoreAttribute), false).Length == 0)
{
if (p.GetCustomAttributes(typeof(NullableAttribute), false).Length == 0)
{
OracleUdt.SetValue(con, pUdt, attributeName, p.GetValue(this, null));
}
else
{
if (p.GetValue(this, null) != null)
{
OracleUdt.SetValue(con, pUdt, attributeName, p.GetValue(this, null));
}
}
}
}
}
public virtual void ToCustomObject(OracleConnection con, IntPtr pUdt)
{
foreach (var p in GetType().GetProperties())
{
// Must ignore these two properties
if (p.Name == "Null" || p.Name == "IsNull") continue;
var oracleObjectMappingAttribute = p.GetCustomAttributes(typeof(OracleObjectMappingAttribute), false)[0] as OracleObjectMappingAttribute;
if (oracleObjectMappingAttribute == null) continue;
var attributeName = oracleObjectMappingAttribute.AttributeName;
if (!OracleUdt.IsDBNull(con, pUdt, attributeName))
{
p.SetValue(this, OracleUdt.GetValue(con, pUdt, attributeName), null);
}
}
}
#region INullable Members
public bool IsNull { get; private set; }
public static TypeTemplate Null
{
get
{
var obj = new TypeTemplate { IsNull = true };
return obj;
}
}
#endregion
}
OracleParameterFactory中UDT参数的方法如下(从代码中去掉异常处理,尽可能呈现干净的代码——产生的错误不是来自异常处理):
public static OracleParameter CreateOutParamForUdtType(
string paramName, string udtName, object value, bool isNullable = false)
{
var param = new OracleParameter
{
ParameterName = paramName,
UdtTypeName = udtName.ToUpperInvariant(),
OracleDbType = OracleDbType.Object,
Direction = ParameterDirection.Output,
IsNullable = isNullable
};
if (value != null)
{
param.Value = value;
}
return param;
}
【问题讨论】:
-
您是否尝试使用 Oracle Developer Tools for Visual Studio 中的代码生成向导对 ERROR_CODE 类型进行完整性检查?
-
另外,您使用的是什么版本的 ODP.NET?
-
@ChristianShay 实际上我没有尝试代码生成向导 - 我会这样做并返回更新。它是适用于 Oracle 12.1 (v. 4.121.2.0) 的 ODP.NET (Oracle.DataAccess.dll) 的 64 位非托管版本。
-
@ChristianShay 吸取的教训 - 自动生成的代码做得很好,可以解决问题。该问题是由未正确处理的具有空值的嵌套 UDT 类型引起的。我决定只使用自动生成的代码,因为它是迄今为止最简单,也许是最有效的方法,尽管我喜欢从完成大部分工作的基类继承的方法(这种方法使用反射,这可能不是一个好的无论如何,例如,可能有数万个 UDT 对象的数组)。感谢您为我指明正确的方向。
-
太棒了!另一条建议是考虑使用过多 UDT 对性能的影响。如果可能,我会保持简单(在可行的情况下使用关联数组或临时表将数据传递给 SP)。
标签: c# oracle stored-procedures odp.net