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]
中数组的每个元素。
我正在尝试调用一个存储过程,该过程采用 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]
中数组的每个元素。