¿Cómo utilizar un cursor de referencia de Oracle de C# ODP.NET como parámetro ReturnValue, sin utilizar una función o procedimiento almacenado?
Frecuentes
Visto 19,161 equipos
5
Necesito ayuda para entender si la forma en que estoy tratando de usar un Ref Cursor como un parámetro ReturnValue para múltiples registros/valores, con PL/SQL simplemente siendo el Texto de comando de un objeto OracleCommand y no en un Procedimiento almacenado o Función, es incluso posible.
Si eso no es posible, lo que estoy tratando de hacer es encontrar una manera de emitir una declaración PL/SQL que actualice una cantidad desconocida de registros (depende de cuántos coincidan con la cláusula WHERE) y devuelva los ID de todos los registros Actualizado en un OracleDataReader, utilizando un solo viaje de ida y vuelta a la base de datos, sin el uso de un procedimiento o función almacenados.
Estoy trabajando con Oracle 11g usando ODP.NET para comunicarme con un código base C# .NET 4.0 existente que usa la conexión SQL para recuperar/modificar datos. La definición de tabla de prueba simplificada que estoy usando se ve así:
CREATE TABLE WorkerStatus
(
Id NUMERIC(38) NOT NULL
,StateId NUMERIC(38) NOT NULL
,StateReasonId NUMERIC(38) NOT NULL
,CONSTRAINT PK_WorkerStatus PRIMARY KEY ( Id )
)
Relleno previamente la tabla con tres valores de prueba así:
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO WorkerStatus (Id, StateId, StateReasonId)
VALUES (1, 0, 0)';
EXECUTE IMMEDIATE 'INSERT INTO WorkerStatus (Id, StateId, StateReasonId)
VALUES (2, 0, 0)';
EXECUTE IMMEDIATE 'INSERT INTO WorkerStatus (Id, StateId, StateReasonId)
VALUES (3, 0, 0)';
END;
La instrucción SQL existente, cargada desde un archivo de secuencia de comandos denominado Oracle_UpdateWorkerStatus2 y contenida en OracleCommand.CommandText tiene este aspecto:
DECLARE
TYPE id_array IS TABLE OF WorkerStatus.Id%TYPE INDEX BY PLS_INTEGER;
t_ids id_array;
BEGIN
UPDATE WorkerStatus
SET
StateId = :StateId
,StateReasonId = :StateReasonId
WHERE
StateId = :CurrentStateId
RETURNING Id BULK COLLECT INTO t_Ids;
SELECT Id FROM t_Ids;
END;
Creé un pequeño programa de prueba en C# para intentar aislar dónde obtengo un error ORA-01036 "nombre/número de variable ilegal" que tiene un cuerpo principal que se ve así:
using System;
using System.Configuration;
using System.Data;
using System.Text;
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;
namespace OracleDbTest
{
class Program
{
static void Main(string[] args)
{
// Load the SQL command from the script file.
StringBuilder sql = new StringBuilder();
sql.Append(Properties.Resources.Oracle_UpdateWorkerStatus2);
// Build and excute the command.
OracleConnection cn = new OracleConnection(ConfigurationManager.ConnectionStrings["OracleSystemConnection"].ConnectionString);
using (OracleCommand cmd = new OracleCommand(sql.ToString(), cn))
{
cmd.BindByName = true;
cn.Open();
OracleParameter UpdatedRecords = new OracleParameter();
UpdatedRecords.OracleDbType = OracleDbType.RefCursor;
UpdatedRecords.Direction = ParameterDirection.ReturnValue;
UpdatedRecords.ParameterName = "rcursor";
OracleParameter StateId = new OracleParameter();
StateId.OracleDbType = OracleDbType.Int32;
StateId.Value = 1;
StateId.ParameterName = "StateId";
OracleParameter StateReasonId = new OracleParameter();
StateReasonId.OracleDbType = OracleDbType.Int32;
StateReasonId.Value = 1;
StateReasonId.ParameterName = "StateReasonId";
OracleParameter CurrentStateId = new OracleParameter();
CurrentStateId.OracleDbType = OracleDbType.Int32;
CurrentStateId.Value = 0;
CurrentStateId.ParameterName = "CurrentStateId";
cmd.Parameters.Add(UpdatedRecords);
cmd.Parameters.Add(StateId);
cmd.Parameters.Add(StateReasonId);
cmd.Parameters.Add(CurrentStateId);
try
{
cmd.ExecuteNonQuery();
OracleDataReader dr = ((OracleRefCursor)UpdatedRecords.Value).GetDataReader();
while (dr.Read())
{
Console.WriteLine("{0} affected.", dr.GetValue(0));
}
dr.Close();
}
catch (OracleException e)
{
foreach (OracleError err in e.Errors)
{
Console.WriteLine("Message:\n{0}\nSource:\n{1}\n", err.Message, err.Source);
System.Diagnostics.Debug.WriteLine("Message:\n{0}\nSource:\n{1}\n", err.Message, err.Source);
}
}
cn.Close();
}
Console.WriteLine("Press Any Key To Exit.\n");
Console.ReadKey(false);
}
}
}
Intenté cambiar los nombres de los parámetros, nombrar y no nombrar el parámetro UpdatedRecords, cambiar el orden para que UpdatedRecords sea el primero o el último. Lo más parecido que he encontrado hasta ahora es la siguiente pregunta de StackOverflow (¿Cómo llamar a una función de Oracle con un cursor de referencia como parámetro de salida desde C #?), pero eso todavía usa una función almacenada por lo que puedo decir.
Al ejecutar el script Oracle_UpdateWorkerStatus2 PL/SQL de SQL Developer, abre el cuadro de diálogo "Enter Binds" donde ingreso los valores para CurentStateId, StateId y StateReasonId como en el código anterior, pero da el siguiente informe de error:
Error report:
ORA-06550: line 13, column 17:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 13, column 2:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
Realmente no entiendo por qué me dice que la tabla no existe, cuando definí la tabla WorkerStatus y declaré que la variable t_Ids, de tipo id_array, también es una tabla. Cualquier ayuda aquí es muy apreciada.
1 Respuestas
5
Intentaré una respuesta en lugar de otro comentario.
Como dije en un comentario, una declaración de selección pura/simple no funciona en PL/SQL. Pero me equivoqué al afirmar que necesita una función almacenada para devolver un cursor de referencia.
Pero lo primero es lo primero: el tipo "id_array" que declara en su bloque PL/SQL es un tipo PL/SQL. No se puede usar en una declaración de selección de cursor de referencia. En su lugar, necesitará un tipo de SQL:
create type id_array as table of number;
Esto debe ejecutarse solo una vez, al igual que "crear tabla".
Su bloque PL/SQL podría verse así:
DECLARE
t_ids id_array;
BEGIN
UPDATE WorkerStatus
SET
StateId = :StateId
,StateReasonId = :StateReasonId
WHERE
StateId = :CurrentStateId
RETURNING Id BULK COLLECT INTO t_Ids;
OPEN :rcursor FOR SELECT * FROM TABLE(cast(t_Ids as id_array));
END;
PS:
Al armar esta publicación, me di cuenta de dónde podría provenir el ORA-00942. La matriz t_ids se basó en un tipo PL/SQL, que no se conoce ni está disponible en el lado de SQL.
Respondido 03 Jul 12, 01:07
Por cierto, para cualquiera que mire esto más tarde, he editado el código C# anterior para que funcione con: rcursor, en lugar de: ReturnValue. Para que el ejemplo funcione, coloque el código C# en una nueva aplicación de consola, reemplace mi PL/SQL del archivo de script Oracle_UpdateWorkerStatus2 con el bloque PL/SQL de Juergen. Ejecute CREATE TYPE una vez como menciona Juergen. A continuación, ejecute la aplicación de consola C# y debería enumerar los 3 ID de los registros como afectados. - Paul
¿Podría ser esto un problema de privilegio/visibilidad? Cuando se conecta manualmente a la cuenta de Oracle en la que estaba ejecutando su secuencia de comandos "Oracle_UpdateWorkerStatus2", ¿puede seleccionar de la tabla Workerstatus? - Juergen Hartelt
Puedo seleccionar de la tabla WorkerStatus, así como soltarlo y crearlo. Soy bastante nuevo en Oracle, así que no sé si hay algún privilegio especial que necesitaría para poder usar los cursores de referencia, pero nuestro técnico de TI/DBA me configuró con todos los privilegios que normalmente usan nuestros desarrolladores, así que no creo que esto sea un problema de privilegios. Pero le preguntaré el lunes. - Paul
Verifiqué con nuestro DBA, y él no tiene conocimiento de ningún privilegio especial requerido para hacer lo que estoy intentando. Además, hizo un escaneo rápido de lo que tengo aquí y no ve ninguna razón por la que no funcione. ¿Alguna idea sobre lo que podría estar haciendo mal u otra forma de lograr los objetivos? - Paul
Lo siento Paul, me engañó el código C#. Entonces, ya estaba ejecutando su script desde SQL-Developer, no llamándolo desde algún código de aplicación. Mirando esa secuencia de comandos, ahora esperaría que la última "selección" provoque una excepción, porque no contiene una cláusula "en". Dentro de PL/SQL no puede simplemente hacer una "selección". El conjunto de resultados debe colocarse en una variable. O necesita usar una función almacenada para pasar los datos a través de su valor de retorno, o necesita ejecutar SQL puro. ¿La declaración de actualización no sería exactamente lo que quieres? "ACTUALIZAR WorkerStatus... REGRESANDO Id;". Sin cláusula "en". - Juergen Hartelt
Gracias Juergen. Primero intenté ejecutar el SQL, a través de ODP.NET, desde una aplicación de consola C#, cuando eso falló, intenté ejecutar solo el SQL desde SQL Developer. Hasta donde yo sé, con ODP.NET, la única forma de devolver varios registros de una actualización a una aplicación es a través de un cursor de referencia o una matriz. Con el Array, parece que tienes que saber el recuento de registros por adelantado, lo que no funcionará para mí. No conozco una forma de dirigir la cláusula Returning para volver a un Ref Cursor. Por lo tanto, no creo que simplemente utilizar la cláusula de devolución pueda funcionar con la aplicación. - Paul