簡體   English   中英

如何在不使用存儲函數或過程的情況下,將C#ODP.NET中的Oracle Ref Cursor用作ReturnValue參數?

[英]How to use an Oracle Ref Cursor from C# ODP.NET as a ReturnValue Parameter, without using a Stored Function or Procedure?

我需要幫助了解如果我嘗試將Ref Cursor用作多個記錄/值的ReturnValue參數的方式(PL / SQL只是OracleCommand對象的CommandText而不是存儲過程或函數中的方式)是否均勻可能。

如果這不可能,我想做的就是找到一種方法來發出PL / SQL語句,該語句將更新未知數量的記錄(取決於與WHERE子句匹配的記錄數),並返回所有記錄在OracleDataReader中進行更新,而無需使用存儲過程或函數,而只需使用一次數據庫往返。

我正在使用ODP.NET與Oracle 11g進行通信,以與使用SQL連接檢索/修改數據的現有C#.NET 4.0代碼庫進行通信。 我使用的簡化測試表定義如下所示:

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問題( 如何使用Ref Cursor作為C#的輸出參數調用Oracle函數? ),但是據我所知,它仍然使用存儲函數。

從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:

當我定義了WorkerStatus表並聲明id_array類型的t_Ids變量也為表時,我真的不明白為什么它告訴我該表不存在。 非常感謝您的任何幫助。

我將嘗試一個答案,而不是其他評論。

正如我在一條評論中所說,純/簡單的選擇語句在PL / SQL中不起作用。 但是我在陳述時是錯誤的,您需要一個存儲的函數來返回引用游標。

但是首先要注意的是:在PL / SQL塊中聲明的“ id_array”類型是PL / SQL類型。 不能在ref游標select語句中使用它。 相反,您將需要一個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端未知/不可用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM