【问题标题】:How to use an Oracle Ref Cursor from C# ODP.NET as a ReturnValue Parameter, without using a Stored Function or Procedure?如何在不使用存储函数或过程的情况下使用 C# ODP.NET 中的 Oracle Ref Cursor 作为 ReturnValue 参数?
【发布时间】:2012-07-01 08:05:12
【问题描述】:

我需要帮助了解我是否尝试将 Ref 游标用作多个记录/值的 ReturnValue 参数,PL/SQL 只是 OracleCommand 对象的 CommandText 而不是存储过程或函数, 甚至是可能的。

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

我正在使用 ODP.NET 使用 Oracle 11g 与现有的 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 )
)

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

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;

从名为 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;

我创建了一个小型 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);
    }
  }
}

我尝试更改参数名称、命名和不命名 UpdatedRecords 参数、更改顺序以使 UpdatedRecords 位于第一个或最后一个。到目前为止我发现的最接近的是以下 StackOverflow 问题 (How to call an Oracle function with a Ref Cursor as Out-parameter from C#?),但据我所知,它仍然使用存储函数。

从 SQL Developer 运行 Oracle_UpdateWorkerStatus2 PL/SQL 脚本,它会打开“Enter Binds”对话框,我在上面的代码中输入 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:

我真的不明白为什么它告诉我该表不存在,当我定义了 WorkerStatus 表并将类型为 id_array 的 t_Ids 变量也声明为表时。非常感谢这里的任何帮助。

【问题讨论】:

  • 这可能是权限/可见性问题吗?当您手动连接到执行脚本“Oracle_UpdateWorkerStatus2”的 oracle 帐户时,您可以从表 workerstatus 中进行选择吗?
  • 我可以从 WorkerStatus 表中进行选择,也可以删除并创建它。我对 Oracle 还很陌生,所以我不知道是否需要任何特殊权限才能使用引用游标,但我们的 IT/DBA 技术人员为我设置了开发人员通常使用的所有权限,所以我不认为这是一个特权问题。不过我会在星期一问他。
  • 与我们的 DBA 核实,他不知道执行我正在尝试的操作所需的任何特殊权限。此外,他快速扫描了我这里的内容,并没有看到它不起作用的任何原因。关于我可能做错了什么,或以其他方式实现目标的任何想法?
  • 对不起,保罗,我被 C# 代码误导了。因此,您已经从 SQL-Developer 执行脚本,而不是从某些应用程序代码中调用它。查看该脚本,我现在预计最后一个“选择”会导致异常,因为它不包含“into”子句。在 PL/SQL 中,您不能只进行“选择”。结果集需要放入一个变量中。您要么需要使用存储函数通过其返回值传递数据,要么需要执行纯 SQL。更新语句不正是您想要的吗? “更新 WorkerStatus ... 返回 ID;”。没有“进入”子句。
  • 谢谢于尔根。我首先尝试通过 ODP.NET 从 C# 控制台应用程序运行 SQL,当失败时,我尝试仅从 SQL Developer 运行 SQL。据我所知,使用 ODP.NET,将多个记录从更新返回到应用程序的唯一方法是通过 Ref 游标或数组。使用数组,您似乎必须预先知道记录数,这对我不起作用。我不知道如何指示 Returning 子句返回到 Ref Cursor。所以我不相信仅仅使用 Returning 子句就可以与应用程序一起使用。

标签: .net oracle c#-4.0 plsql odp.net


【解决方案1】:

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

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

但首先要做的是:您在 PL/SQL 块中声明的类型“id_array”是 PL/SQL 类型。它不能在引用游标选择语句中使用。相反,您需要一个 SQL 类型:

create type id_array as table of number;

这只需执行一次,就像“创建表”一样。

您的 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;

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

【讨论】:

  • 顺便说一句,对于稍后查看此内容的任何人,我已经编辑了上面的 C# 代码以使用 :rcursor,而不是 :ReturnValue。要使示例工作将 C# 代码放入新的控制台应用程序,请将 Oracle_UpdateWorkerStatus2 脚本文件中的 PL/SQL 替换为 Juergen 的 PL/SQL 块。正如 Juergen 所说,执行一次 CREATE TYPE。然后运行 ​​C# 控制台应用程序,它应该列出受影响的记录的 3 个 id。
猜你喜欢
  • 1970-01-01
  • 2012-01-13
  • 2012-01-23
  • 1970-01-01
  • 1970-01-01
  • 2022-08-24
  • 1970-01-01
  • 1970-01-01
  • 2022-07-21
相关资源
最近更新 更多