ODP.Net - 使用自定义类型参数调用存储过程抛出 ORA-06550/PLS-00306

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

我正在尝试调用一个存储过程,该过程采用 SYS_REFCURSOR 输出参数和自定义类型的输入参数。现在,当我尝试从我的应用程序调用该过程时,出现此错误:

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

这是创建自定义类型和我尝试调用的存储过程的 PL/SQL 脚本:

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

我在 C# 方面尝试过各种方法和迭代,但这是我到目前为止想出的:

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

我能够重现错误,我发现传递数组参数的 [主要] 问题在 t_string_list 的类型声明中。您需要通过添加 INDEX BY BINARY_INTEGER 使其成为索引数组而不是关联数组。为了做到 that,您必须将类型定义移动到包中,因为该子句似乎在包外不受支持。

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;

这样至少得到了参数的传递。但是,一旦执行该过程,我在使用 TABLE() 运算符将列表查询为 table 时遇到另一个错误。

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

解决方案 here 说将列表分配给临时变量会以某种方式使其工作。为什么?我猜是甲骨文。不过,严肃地说,它可能会被延迟加载,并将其分配给一个临时文件会导致它完全读取参数数据。不过,这只是一个可行的理论,我们欢迎更好的解决方案(带解释)。

无论该解决方法为何有用,这都有效:

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;

在 C# 方面,cmd.ArrayBindCount 显然不是宣传的内容。分配时出现严重错误:

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

所以我在深入研究类型和过程定义之前去掉了它,这让我遇到了你上面报告的错误。所以就参数本身而言,你所拥有的是正确的。

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

Count 属性 是可选的,但如果您为其分配的值小于您要传递的元素数,它只会传递您指定的元素。最好不要分配它。

但是,对于 output 数组,我猜你需要设置 Count 属性 来告诉它元素的最大数量在输出 ArrayBindSize 上指定每个元素的最大大小。

只需将数组元素选择到游标中,就像在我更简单的过程版本中一样,我能够在 while 循环中观察 reader[0] 中数组的每个元素。