0

I'm trying to create a test app that will pass an array of objects (in my case Employees) to an Oracle stored procedure.

When I call the ExecuteReader to call the procedure, I get an error

Column contains NULL data

but I have no idea what that means.

Here is the code I use. I have the following class:

[OracleCustomTypeMapping("EMTEST")]
public class Employee : IOracleCustomType, INullable
{

    public Employee(int? Id, string Name)
    {
        this.Id = Id;
        this.Name = Name;
    }

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

    [OracleObjectMapping("Id")]
    public int? Id { get; set; }

    public bool IsNull { get; set; }

    public static Employee Null => new Employee(0, "") { IsNull = true };

    public void FromCustomObject(OracleConnection con, object udt)
    {
        OracleUdt.SetValue(con, udt, "Id", Id);
        OracleUdt.SetValue(con, udt, "Name", Name);
    }

    public void ToCustomObject(OracleConnection con, object udt)
    {
        Id = ((int?)(OracleUdt.GetValue(con, udt, "Id")));
        Name = ((string)(OracleUdt.GetValue(con, udt, "Name")));
    }
}

On oracle side i create a type:

create or replace TYPE EMTEST AS OBJECT
(
     Id number  null,
     Name varchar2(50)  null    
);

And a procedure in a package called PKS_CARAT

This is the header (where the table of the type is defined) :

type T_EMTEST_ARRAY is table of EMTEST;

procedure TestGetObjectsBack(
        Employes in T_EMTEST_ARRAY,
        ResultTest OUT RC_Result
);

This is the body:

procedure TestGetObjectsBack(
        Employes in T_EMTEST_ARRAY,
        ResultTest OUT RC_Result
)
is
    
begin

OPEN ResultTest FOR
select 1 as Id,'mika' as Name from dual;

-- select e.Id,e.Name from table (Employes) e;
    
end TestGetObjectsBack;

This is the code that calls the stored procedure:

var oracleConnection = new OracleConnection("User Id=xxx;Password=xxx;Data Source=ORAKISDEV2;Connection Lifetime=10;Min Pool Size=1;Max Pool Size=200;");

var employeList = new List<Employee>()
{
    new Employee(1,"Zoki"),
    new Employee(2, "Pera"),
};

var employeArray = employeList.ToArray();

var employeParam = new OracleParameter("Employes", OracleDbType.Object,  ParameterDirection.Input);
employeParam.CollectionType = OracleCollectionType.PLSQLAssociativeArray;

employeParam.Size = employeArray.Length;
employeParam.UdtTypeName = "PKS_CARAT.T_EMTEST_ARRAY";
employeParam.Value = employeArray;

var cursor = new OracleParameter("ResultTest", OracleDbType.RefCursor, ParameterDirection.Output);

oracleConnection.Open();
using (var oracleCommand = oracleConnection.CreateCommand())
{
    oracleCommand.Connection = oracleConnection;
    oracleCommand.CommandText = "PKS_CARAT.TestGetObjectsBack";
    oracleCommand.CommandType = CommandType.StoredProcedure;
    oracleCommand.BindByName = true;

    oracleCommand.Parameters.Add(employeParam);
    oracleCommand.Parameters.Add(cursor);


var reader = oracleCommand.ExecuteReader();
....

I read many articles, and the most important are:

  1. pass array of objects to oracle stored procedure using ODP.Net managed client
  2. Why do I get "Invalid Parameter binding" when using Oracle stored procedure with Udt and C#

I tried many sugesstions but none of them made any difference. I still get the Column contains NULL data without any explanation which data is null or why this issue is caused. I checked if my columns from the class and the oracle type are the same and it look slike they are.

Full exception text is: 'Column contains NULL data'. From 'Oracle.ManagedDataAccess'

Type of the exception is System.InvalidCastException

Im using nuget package Oracle.ManagedDataAccess.Core version 3.21.80

Here is the top of the stack trace of the exception:

OracleInternal.UDT.Types.UDTNamedType.GetNamedTypeMetaData(OracleConnection conn, OracleCommand getTypeCmd)
   at OracleInternal.UDT.Types.UDTTypeCache.GetMetaData(OracleConnection conn, UDTNamedType udtType)
   at OracleInternal.UDT.Types.UDTTypeCache.CreateUDTType(OracleConnection conn, String schemaName, String typeName)
   at OracleInternal.UDT.Types.UDTTypeCache.GetUDTType(OracleConnection conn, String schemaName, String typeName)
   at OracleInternal.ConnectionPool.OraclePoolManager.GetUDTType(OracleConnection conn, String schemaName, String typeName)
   at Oracle.ManagedDataAccess.Client.OracleConnection.GetUDTTypeFromCache(String schemaName, String typeName)
   at Oracle.ManagedDataAccess.Client.OracleParameter.GetUDTType(OracleConnection conn)
   at Oracle.ManagedDataAccess.Client.OracleParameter.PreBind_UDT(OracleConnection conn)
   at Oracle.ManagedDataAccess.Client.OracleParameter.PreBind(OracleConnectionImpl connImpl, ColumnDescribeInfo cachedParamMetadata, Boolean& bMetadataModified, Int32 arrayBindCount, ColumnDescribeInfo& paramMetaData, Object& paramValue, Boolean isEFSelectStatement, SqlStatementType stmtType)
   at OracleInternal.ServiceObjects.OracleCommandImpl.InitializeParamInfo(ICollection paramColl, OracleConnectionImpl connectionImpl, ColumnDescribeInfo[] cachedParamMetadata, Boolean& bMetadataModified, Boolean isEFSelectStatement, MarshalBindParameterValueHelper& marshalBindValuesHelper)
   at OracleInternal.ServiceObjects.OracleCommandImpl.ProcessParameters(OracleParameterCollection paramColl, OracleConnectionImpl connectionImpl, ColumnDescribeInfo[] cachedParamMetadata, Boolean& bBindMetadataModified, Boolean isEFSelectStatement, MarshalBindParameterValueHelper& marshalBindValuesHelper)
   at OracleInternal.ServiceObjects.OracleCommandImpl.ExecuteReader(String commandText, OracleParameterCollection paramColl, CommandType commandType, OracleConnectionImpl connectionImpl, OracleDataReaderImpl& rdrImpl, Int32 longFetchSize, Int64 clientInitialLOBFS, OracleDependencyImpl orclDependencyImpl, Int64[] scnForExecution, Int64[]& scnFromExecution, OracleParameterCollection& bindByPositionParamColl, Boolean& bBindParamPresent, Int64& internalInitialLOBFS, Int64 internalInitialJSONFS, OracleException& exceptionForArrayBindDML, OracleConnection connection, IEnumerable`1 adrianParsedStmt, Boolean isDescribeOnly, Boolean isFromEF)
   at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteReader(Boolean requery, Boolean fillRequest, CommandBehavior behavior)
   at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteReader()
   at OracleArrayTest.Program.TestCollectionCustom() in C:\project\BulkProject\OracleArrayTest\Program.cs:line 203

Does anybody know what am i doing wrong? What is this exception telling me?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Birtija
  • 87
  • 3
  • 10
  • Can you show full call stack? ODP.NET can be decompiled and you can understand why execution is raised. – Svyatoslav Danyliv Jan 27 '23 at 15:18
  • @SvyatoslavDanyliv i added the full stacktrace and will try to debug the source code. – Birtija Jan 28 '23 at 12:24
  • Which version of ODP.NET do you use? – Svyatoslav Danyliv Jan 28 '23 at 13:49
  • And post full exception text, including type of exception. – Svyatoslav Danyliv Jan 28 '23 at 13:57
  • @SvyatoslavDanyliv Im using nuget package Oracle.ManagedDataAccess.Core version 3.21.80, the full exception message is 'Column contains NULL data' and type of exception is System.InvalidCastException – Birtija Jan 30 '23 at 13:24
  • I tried to look at the decompiled code, but its very hard to figure out what is causing the issue. @SvyatoslavDanyliv do you have any other suggestions? – Birtija Jan 30 '23 at 16:12
  • Will take a look tomorrow. – Svyatoslav Danyliv Jan 30 '23 at 16:39
  • @SvyatoslavDanyliv did you have some time to take a look? I will install the Oracle developer tool for VS and try to generate my class from the EMTEST type itself, and try to use it. – Birtija Jan 31 '23 at 15:26
  • Not sure but from source code I see that it is made request to the server and it is returned NULL value for required field. Check that schema is valid or try set schema name to current user. – Svyatoslav Danyliv Feb 01 '23 at 08:54
  • @SvyatoslavDanyliv do you mean adding schema like this employeParam.UdtTypeName = "CurentUserSchema.PKS_CARAT.T_EMTEST_ARRAY"; and oracleCommand.CommandText = "CurentUserSchema.PKS_CARAT.TestGetObjectsBack"; but i get the same issue – Birtija Feb 01 '23 at 09:44
  • 1
    `PKS_CARAT` is schema name? Check `CurentUser.T_EMTEST_ARRAY`. – Svyatoslav Danyliv Feb 01 '23 at 09:47
  • 1
    PKS_CARAT is the package name because the T_EMTEST_ARRAY table is defined there. If i try CurentUser.T_EMTEST_ARRAY i get 'CurentUser.T_EMTEST_ARRAY is invalid' – Birtija Feb 01 '23 at 09:51
  • I generated a class from the type EMTEST with Oracle Dev tools for visual studio, and now i use this class instead of the Employee.cs that i created. Still get the same error. – Birtija Feb 02 '23 at 13:16
  • Not an Oracle guy, so please post all scripts, to just run on my database. – Svyatoslav Danyliv Feb 02 '23 at 13:24
  • 1
    @SvyatoslavDanyliv I found the solution. You can take a look at my answer. Thank you for the help – Birtija Feb 06 '23 at 15:15

1 Answers1

2

I found this article:

And somebody in the comments gives a link to examples on how to send arrays of custom objects to oracle(best example for me was Nested-Table.cs):

After reading the comments and looking at the example i changed the following:

  • I defined the T_EMTEST_ARRAY also in the Types, where i have defined EMTEST ( the body looks like this CREATE OR REPLACE TYPE "T_EMTEST_ARRAY" AS TABLE OF EMTEST;

  • I downloaded and installed the extension Oracle Dev tools for Visual Studio ( best if you install it with c++ module from visual studio installer)

  • I used the extension to generate classes from oracle for T_EMTEST_ARRAY and EMTEST ( you connect to the db with the tool and then can generate c# code from types)

  • I removed the T_EMTEST_ARRAY defined in the package PKS_CARAT because its now define on a global level

  • I deleted the Employee that i created by myself

  • I used the generated classes to create an array. Here is the c# code:

             var emp1 = new EMTEST();
    
             emp1.IME = "Zika";
             emp1.JMBG = 1;
    
             var emp2 = new EMTEST();
    
             emp2.JMBG = 2;
             emp2.IME = "Mika";
    
             EMTEST[] emtestArray = new EMTEST[] { emp1, emp2 };
    
             OracleParameter param = new OracleParameter();
             param.OracleDbType = OracleDbType.Array;
             param.Direction = ParameterDirection.Input;
    
             // Note: The UdtTypeName is case-senstive
             param.UdtTypeName = "T_EMTEST_ARRAY";
             param.Value = emtestArray;
    
             var cursor = new OracleParameter("ResultTest", OracleDbType.RefCursor, ParameterDirection.Output);
    
    
             oracleConnection.Open();
             using (var oracleCommand = oracleConnection.CreateCommand())
             {
                 oracleCommand.Connection = oracleConnection;
                 oracleCommand.CommandText = "PKS_CARAT.TestGetObjectsBack";
                 oracleCommand.CommandType = CommandType.StoredProcedure;
    
                 oracleCommand.Parameters.Add(param);
    
                  oracleCommand.Parameters.Add(cursor);
    
    
                 var reader = oracleCommand.ExecuteReader();
    
                 while (reader.Read())
                 {
                     var jmbg = reader[0];
                     var ime = reader[1];
                 }
                 reader.Close();
             }
    

after that it worked. The hard part was to find the right thread. You can read more about oracle introducing the UDTs for .net core here:

Birtija
  • 87
  • 3
  • 10