简体   繁体   中英

How to get ODP.NET to return null value for User Defined Type from out parameter in Oracle stored procedure?

I am having some trouble with Odp.net as regards the UDT (User Defined Type) behavior.

My problem arises when I have a procedure containing an OUT parameter that returns a specific UDT .

When I return an instantiated UDT for the OUT parameter there is no problem .

When I return a NULL value for the OUT parameter I get a NullReference error :

A first chance exception of type 'System.NullReferenceException' occurred in Oracle.DataAccess.dll

I have tried setting IsNullable = True on the relevant OracleCommand parameter without any success.

I have no problem sending and receiving rather complex UDT's, eg UDT's with nested UDT's and collections of objects and UDT's witgh nested collections of objects.

Any ideas if this can be solved using ODP.NET other than making the Oracle procedure return an instance of the object type?

UPDATE - SOLVED:

The issue was that the UDT type with its nested UDT type was not correctly initialized to null. Using the auto generated code solved the problem. Using Oracle User-Defined Types with .NET and Visual Studio

Christian Shay , thank you for resolving the issue - the auto generated code is perhaps a better choice than implementing base classes to handle most of the behavior, although this is possible.

The Oracle stored procedure signature is:

PROCEDURE CREATE_DEFINITIONS_FOR_GROUP(
    P_GRP_NO             IN     NUMBER
   ,P_DATE               IN     DATE
   ,P_ERROR_CODE         OUT    MYSCHEMA.ERROR_CODE);

I call this code in C# using ODP.NET after opening a connection:

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);
    }
}

The UDT type is defined as a valid UDT type in .NET like this:

public class DbErrorCode : TypeTemplate
{
    [OracleObjectMapping("ERROR_CODE")]
    public decimal Code { get; set; }

    [OracleObjectMapping("DESCRIPTION")]
    public string Description { get; set; }
}

The base TypeTemplate class is defined like this:

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
}

The method in OracleParameterFactory for the UDT parameter is as follows (exception handling removed from the code to present as clean code as possible - the error produced does not come from the exception handling):

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;
}

Problem solved : The issue was that the UDT type with its nested UDT type was not correctly initialized to null. Using the auto generated code solved the problem.Using Oracle User-Defined Types with .NET and Visual Studio

Christian Shay , thank you for resolving the issue - the auto generated code is perhaps a better choice than implementing base classes to handle most of the behavior, although this is possible.

I am not certain on this, but my experience is that "oCmd.ExecuteNonQuery()" is likely returning null and needs to be returned to an object and you can then check if it is null and return and empty UDT or the on returned. Try

TypeTemplate udtOnbj = new TypeTemplate();

object testObj = oCmd.ExecuteNonQuery();

if ( testObj == null ) { return udtObj; } else { return testObj; }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM