问题
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