如何使用C#ODP.NET中的Oracle Ref Cursor作为ReturnValue参数,而不使用存储函数或过程?

Pau*_*aul 5 .net oracle plsql odp.net c#-4.0

我需要帮助的理解,如果我试图用一个参考光标作为一个返回值参数多个记录/值的方式,与PL/SQL只是作为一个OracleCommand对象的CommandText,而不是在存储过程或函数,即使是可能.

如果这是不可能的,我正在尝试做的是找到一种方法来发出一个PL/SQL语句,它将更新未知数量的记录(取决于WHERE子句匹配的数量),并返回所有的ID记录在OracleDataReader中更新,使用数据库的单次往返,而不使用存储过程或函数.

我正在使用Oracle 11g使用ODP.NET与现有的C#.NET 4.0代码库进行通信,该代码库使用SQL连接来检索/修改数据.我正在使用的简化测试表定义如下所示:

CREATE TABLE WorkerStatus
(
    Id                  NUMERIC(38)         NOT NULL
    ,StateId            NUMERIC(38)         NOT NULL
    ,StateReasonId      NUMERIC(38)         NOT NULL
    ,CONSTRAINT PK_WorkerStatus PRIMARY KEY ( Id )
)
Run Code Online (Sandbox Code Playgroud)

我预先用三个测试值填充表,如下所示:

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;
Run Code Online (Sandbox Code Playgroud)

从名为Oracle_UpdateWorkerStatus2的脚本文件加载并包含在OracleCommand.CommandText中的现有SQL语句如下所示:

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;
Run Code Online (Sandbox Code Playgroud)

我已经创建了一个小的C#测试程序来尝试隔离我收到的ORA-01036"非法变量名称/数字"错误,其主体看起来像这样:

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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试更改参数名称,命名和不命名UpdatedRecords参数,更改顺序以使UpdatedRecords是第一个或最后一个.我到目前为止发现的最接近的是下面的StackOverflow问题(如何调用一个Oracle函数中Ref光标从C#out参数?),但仍然使用存储功能,据我可以告诉.

从运行SQL Developer中Oracle_UpdateWorkerStatus2 PL/SQL脚本,它会打开"输入绑定"对话框,在这里我输入CurentStateId,STATEID和StateReasonId的值在上面的代码,但它提供了以下错误报告:

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:
Run Code Online (Sandbox Code Playgroud)

我真的不明白为什么它告诉我表不存在,当我定义了WorkerStatus表,并且声明类型为id_array的t_Ids变量时也是一个表.非常感谢任何帮助.

Jue*_*elt 5

我会尝试一个答案,而不是另一个评论.

正如我在一篇评论中所说,纯/简单的select语句在PL/SQL中不起作用.但我说错了,你需要一个存储函数来返回一个引用游标.

但首先要做的是:您在PL/SQL块中声明的类型"id_array"是PL/SQL类型.它不能在ref cursor select语句中使用.相反,您将需要一个SQL类型:

create type id_array as table of number;
Run Code Online (Sandbox Code Playgroud)

这只需要执行一次,就像"创建表"一样.

您的PL/SQL块可能如下所示:

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;
Run Code Online (Sandbox Code Playgroud)

PS:
在收集这篇文章时,我意识到ORA-00942可能来自哪里.数组t_ids基于PL/SQL类型,在SQL端未知/可用.