ODP.Net - Calling stored procedure with custom type parameter throws ORA-06550/PLS-00306

我的梦境 提交于 2020-01-15 11:46:05

问题


I'm trying to call a stored procedure that takes a SYS_REFCURSOR output parameter and an input parameter with a custom type. Right now when I try to call the procedure from my application I get this error:

ORA-06550: line 1, column 7:
PLS-00306: wrong number or types of arguments in call to 'SP_TEST_01'
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

This is the PL/SQL script that creates the custom type and the stored procedure I'm trying to call:

CREATE OR REPLACE TYPE t_string_list AS TABLE OF VARCHAR2(4000);
/

CREATE OR REPLACE PACKAGE TEST_PACKAGE_01
AS
    PROCEDURE SP_TEST_01(in_list IN t_string_list, out_cursor OUT SYS_REFCURSOR);
END TEST_PACKAGE_01;
/

CREATE OR REPLACE PACKAGE BODY TEST_PACKAGE_01
AS
    PROCEDURE SP_TEST_01(in_list IN t_string_list, out_cursor OUT SYS_REFCURSOR)
    IS
    BEGIN
        OPEN out_cursor FOR
            SELECT st.*
            FROM TABLE(in_list) t
            JOIN SOME_TABLE st ON st.SOME_COLUMN = t.COLUMN_VALUE;
    END SP_TEST_01;
END TEST_PACKAGE_01;
/

I've messed around with a variety of approaches and iterations on the C# side of things, but this is what I've come up with so far:

using (var context = new SomeDbContext())
{
    using (var conn = new OracleConnection(context.Database.Connection.ConnectionString))
    {
        conn.Open();
        var cmd = conn.CreateCommand();
        cmd.CommandText = "TEST_PACKAGE_01.SP_TEST_01";
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.ArrayBindCount = values.Count; // values is a List<string>

        cmd.Parameters.Add(new OracleParameter
        {
            OracleDbType = OracleDbType.Varchar2,
            Direction = ParameterDirection.Input,
            CollectionType = OracleCollectionType.PLSQLAssociativeArray,
            Value = values.ToArray(),
            Size = values.Count
        });

        cmd.Parameters.Add(new OracleParameter()
        {
            OracleDbType = OracleDbType.RefCursor,
            Direction = ParameterDirection.Output
        });

        using (var reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
              // do stuff here              
            }
        }
    }
}

回答1:


I was able to reproduce the error and I discovered that the [main] problem with passing the array parameter is in the type declaration of t_string_list. You need to make it an indexed array rather than an associative one by adding INDEX BY BINARY_INTEGER. In order to do that, you have to move the type definition into the package because that clause doesn't seem to be supported outside of a package.

CREATE OR REPLACE PACKAGE TEST_PACKAGE_01
AS
    TYPE t_string_list IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;

    PROCEDURE SP_TEST_01(in_list IN t_string_list, out_cursor OUT SYS_REFCURSOR);
END TEST_PACKAGE_01;

This at least gets the parameter passed. Once executing the procedure, however, I got another error in querying the list as a table using the TABLE() operator.

ORA-21700: object does not exist or is marked for delete

The solution here says that assigning the list to a temporary variable somehow causes it to work. Why? Oracle, I guess. In all seriousness, though, it may be delay-loaded and assigning it to a temporary causes it to fully read the parameter data. That's just a working theory, though, and a better solution (with explanation) would be welcomed.

Regardless of why that workaround helped, this worked:

CREATE OR REPLACE PACKAGE BODY TEST_PACKAGE_01
AS
    PROCEDURE SP_TEST_01(in_list IN t_string_list, out_cursor OUT SYS_REFCURSOR)
    IS
        temp_list t_string_list := in_list;
    BEGIN
        OPEN out_cursor FOR
            SELECT t.COLUMN_VALUE
            FROM TABLE(temp_list) t;
            -- took out the join for testing
    END SP_TEST_01;
END TEST_PACKAGE_01;

On the C# side, cmd.ArrayBindCount isn't what's advertised, apparently. I got a nasty error when I assigned it:

ORA-03137: malformed TTC packet from client rejected: ...

So I got rid of that before digging into the type and procedure definitions and that got me to the error you reported above. So as far as the parameteter itself, what you had was right.

cmd.Parameters.Add(new OracleParameter()
{
    OracleDbType = OracleDbType.Varchar2,
    Direction = ParameterDirection.Input,
    CollectionType = OracleCollectionType.PLSQLAssociativeArray,
    Value = values.ToArray()
});

The Count property is optional but if you assign it a value less than the number of elements you want to pass, it will only pass what you specify. Best to leave it unassigned.

For an output array, however, I'm guessing you would need to set the Count property to tell it the maximum number of elements on output and ArrayBindSize to specify the maximum size of each element.

Simply selecting the array elements into the cursor, as in my even-simpler version of your procedure, I was able to observe each element of the array in reader[0] within the while loop.



来源:https://stackoverflow.com/questions/57580188/odp-net-calling-stored-procedure-with-custom-type-parameter-throws-ora-06550-p

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!