【问题标题】:How to get ODP.NET to return null value for User Defined Type from out parameter in Oracle stored procedure?如何让 ODP.NET 从 Oracle 存储过程中的 out 参数返回用户定义类型的空值?
【发布时间】: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


【解决方案1】:

对此我不确定,但我的经验是“oCmd.ExecuteNonQuery()”可能返回 null 并且需要返回给一个对象,然后您可以检查它是否为 null 并返回并为空 UDT 或在返回。试试

TypeTemplate udtOnbj = new TypeTemplate();

object testObj = oCmd.ExecuteNonQuery();

if ( testObj == null ) { return udtObj; } 其他 { 返回 testObj; }

【讨论】:

  • 不幸的是,这不起作用@NicholasSwandel - 我试过了。这是对 oCmd.ExecuteNonQuery() 的调用失败,因此即使我设置了一个变量,它也不会将其值分配给变量。
【解决方案2】:

问题已解决:问题在于具有嵌套 UDT 类型的 UDT 类型未正确初始化为 null。使用自动生成的代码解决了这个问题。在 .NET 和 Visual Studio 中使用 Oracle 用户定义类型

Christian Shay,感谢您解决问题 - 自动生成的代码可能是比实现基类来处理大部分行为更好的选择,尽管这是可能的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多