【问题标题】:Returning cursor from PL/SQL anonymous block in C#从 C# 中的 PL/SQL 匿名块返回游标
【发布时间】:2014-05-14 07:21:19
【问题描述】:

我正在将现有应用程序从 SQL Server 转换为 Oracle,但遇到了障碍。我正在尝试将匿名块作为动态 SQL 执行并返回结果集。然而,我尝试过的任何东西似乎都无法返回任何值。由于设计限制,存储过程已被淘汰。

我的查询定义为:

DECLARE type id_array IS TABLE OF number;
t_Ids id_array;
BEGIN
UPDATE CSM_RECORDS SET MIGRATION_STATE = 1, LAST_UPDATE = CURRENT_DATE
WHERE OBJECT_UID IN 
(SELECT OBJECT_UID 
FROM CSM_RECORDS obj 
WHERE MIGRATION_STATE = 0
AND ROWNUM <= :BatchSize)
AND (:BatchName IS NULL OR obj.BATCH_NAME = :BatchName)
RETURNING OBJECT_UID BULK COLLECT INTO t_Ids;

OPEN rcursor FOR SELECT * FROM CSM_RECORDS;-- WHERE OBJECT_UID IN (t_Ids);
END;

你可以看到我已经注释掉了光标上的 WHERE 子句,试图让 anything 完全返回。

在 C# 方面,我有:

OracleCommand getNextNodesC = new OracleCommand(SQL_AS_SHOWN_ABOVE, conn);
getNextNodesC.BindByName = true;

OracleParameter batchSizeP = new OracleParameter("BatchSize", OracleDbType.Int32);
batchSizeP.Value = batchSize;

getNextNodesC.Parameters.Add(batchSizeP);

OracleParameter batchNameP = new OracleParameter("BatchName", OracleDbType.Varchar2);
batchNameP.Value = batchName;

getNextNodesC.Parameters.Add(batchNameP);


OracleParameter returnCursor = new OracleParameter("rcursor", OracleDbType.RefCursor);
returnCursor.Direction = ParameterDirection.Output;
getNextNodesC.Parameters.Add(returnCursor);

getNextNodesC.ExecuteNonQuery();

return ((Oracle.ManagedDataAccess.Types.OracleRefCursor)returnCursor.Value).GetDataReader();

最终目标是我可以使用的DbDataReader,但在上面的代码中,returnCursor.Value 似乎仍然为空。我尝试了OutputReturnValue 参数和ExecuteNonQuery()ExecuteReader() 的各种组合,但无济于事。

任何指针都将不胜感激,但实际上可以完成我正在寻找的代码示例将是壮观的。

【问题讨论】:

  • OPEN CURSOR 之后的代码在哪里。 OPEN Cursor 只会设置光标并使其准备好从中读取,它不会将任何数据返回给调用者
  • 那么我将如何将数据返回给调用者?很有可能是我的问题。我试过在OPEN... 行之后做一个RETURN rcursor;,但这似乎并没有改变任何东西。
  • 尝试按位置绑定以将其从图片中删除,另请参阅 oracle 主页中 odp.net 示例目录中的参考光标示例代码。
  • ..还大大简化了 sql,所以你可以保证一些行会回来(摆脱嵌套选择)
  • 按位置绑定会导致ORA-00932: inconsistent datatypes: expected - got CURSER 错误。我不知道这是离解决方案更近了一步,还是离解决方案更近了一步。编辑:我实际上在游标语句中注释掉了整个 WHERE 子句(见上文),以保证我应该得到一些回报。

标签: c# sql oracle plsql odp.net


【解决方案1】:

TLDR:您的光标绑定变量缺少冒号:

OPEN :rcursor FOR SELECT * FROM CSM_RECORDS;-- WHERE OBJECT_UID IN (t_Ids);

完整答案: 您可能知道,在 oracle 中有 pl/sql 上下文(您的匿名块)和 SQLplus 上下文。这是一个带有 SQLplus vars 的块,带有相关绑定变量的块,以及末尾的 SQLplus 打印:

var rcursor refcursor
var fromDate varchar2(50)
var toDate varchar2(50)
exec :fromDate := '1-mar-2014';
exec :toDate := '1-apr-2014';

begin
  open :rcursor for
    SELECT
     trunc(to_date(:fromDate,'dd-mon-yyyy')) + NUMTODSINTERVAL(n,'day') AS Full_Date
    FROM (
    select (level-1) n
    from dual
    connect by level-1 <= trunc(to_date(:toDate,'dd-mon-yyyy')) - trunc(to_date(:fromDate,'dd-mon-yyyy'))
   )
  ;
end;
/
print rcursor

在 .net 中执行块时,ODP.net 负责在 SQLplus 级别完成的准备工作。这是从 .net 执行的相同块(作为 nunit 测试):

[Test]
public void RefCursorFromBatch()
{
  OracleCommand cmd = new OracleCommand();
  cmd.CommandText = @"
  begin
    open :rcursor for
      SELECT
       trunc(to_date(:fromDate,'dd-mon-yyyy')) + NUMTODSINTERVAL(n,'day') AS Full_Date
      FROM (
      select (level-1) n
      from dual
      connect by level-1 <= trunc(to_date(:toDate,'dd-mon-yyyy')) - trunc(to_date(:fromDate,'dd-mon-yyyy'))
     )
    ;
  end;";
  cmd.BindByName = true;
  cmd.Parameters.Add("fromDate", OracleDbType.Date).Value = DateTime.Today.AddDays(-30);
  cmd.Parameters.Add("toDate", OracleDbType.Date).Value = DateTime.Today;
  cmd.Parameters.Add("rcursor", OracleDbType.RefCursor).Direction = ParameterDirection.Output;

  using (cmd.Connection = new OracleConnection("..."))
  {
    cmd.Connection.Open();
    var reader = cmd.ExecuteReader();

    OracleDataAdapter da = new OracleDataAdapter(cmd);
    DataTable dt = new DataTable();
    da.Fill(dt);

    Assert.Greater(dt.Rows.Count, 0);

  }
}

您可以在这里阅读更多内容:http://www.brothersincode.com/post/executing-SQL-Plus-Batches-from-Net.aspx

【讨论】:

  • 是否可以用类似的方式执行SP?例如@" 执行 My_PROC(:Param1,:Param2, :Param3);"而 Param3 是 My_Proc 中的输出参数
【解决方案2】:

如果您还没有答案,这里有一些示例代码,我已经确认可以正常工作,您可以将其用作起点。

using System;
using System.Data;
using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;

namespace ConsoleApplication1
{
  class Class1
  {

    [STAThread]
    static void Main(string[] args)
    {
      try
      { 
        string conString = "User Id=scott;Password=tiger;Data Source=orcl;Pooling=false;";

        OracleConnection con = new OracleConnection();
        con.ConnectionString = conString;
        con.Open();

        string cmdtxt = "BEGIN " +
          "OPEN :1 for select ename, deptno from emp where deptno = 10; " +
          "OPEN :2 for select ename, deptno from emp where deptno = 20; " +
          "OPEN :3 for select ename, deptno from emp where deptno = 30; " +
          "END;";

        OracleCommand cmd = con.CreateCommand();
        cmd.CommandText = cmdtxt;

        OracleParameter p1 = cmd.Parameters.Add("refcursor1",
            OracleDbType.RefCursor);
        p1.Direction = ParameterDirection.Output;

        OracleParameter p2 = cmd.Parameters.Add("refcursor2",
            OracleDbType.RefCursor);
        p2.Direction = ParameterDirection.Output;

        OracleParameter p3 = cmd.Parameters.Add("refcursor3",
            OracleDbType.RefCursor);
        p3.Direction = ParameterDirection.Output;


        cmd.ExecuteNonQuery();


        OracleDataReader dr1 =
          ((OracleRefCursor)cmd.Parameters[2].Value).GetDataReader();
        OracleDataReader dr2 =
          ((OracleRefCursor)cmd.Parameters[1].Value).GetDataReader();


        while (dr1.Read() && dr2.Read())
        {
          Console.WriteLine("Employee Name: " + dr1.GetString(0) + ", " +
              "Employee Dept:" + dr1.GetDecimal(1));
          Console.WriteLine("Employee Name: " + dr2.GetString(0) + ", " +
              "Employee Dept:" + dr2.GetDecimal(1));
          Console.WriteLine();
        }

        Console.WriteLine("Press 'Enter' to continue");
        Console.ReadLine();


      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
        Console.WriteLine(ex.InnerException);
        Console.WriteLine(ex.Data);
      }
    }
  }
}

【讨论】:

    【解决方案3】:

    试试这个作为你的动态 sql...

    DECLARE type id_array IS TABLE OF number;
    t_Ids id_array;
    BEGIN
    UPDATE CSM_RECORDS SET MIGRATION_STATE = 1, LAST_UPDATE = CURRENT_DATE
    WHERE OBJECT_UID IN 
    (SELECT OBJECT_UID 
    FROM CSM_RECORDS obj 
    WHERE MIGRATION_STATE = 0
    AND ROWNUM <= :BatchSize)
    RETURNING OBJECT_UID BULK COLLECT INTO t_Ids;
    
    SELECT * FROM CSM_RECORDS;-- WHERE OBJECT_UID IN (t_Ids);
    END;
    

    【讨论】:

    • 没有骰子。没有错误,但returnCursor.Value 仍然为空。尝试ExecuteReader() 仍然返回没有行的阅读器。
    【解决方案4】:

    感谢大家的反馈。事实证明,在调试此问题时,有时我切换了定义我的查询的变量,因此我对查询所做的更改实际上并没有在运行应用程序时应用。结果,我不确定最终是哪个修复程序成为了答案。但为了完整起见,这就是我最终使用的方法。

    首先,运行:

    create type id_array as table of number;
    

    (来自this question的回答)

    查询:

    DECLARE t_Ids id_array;
    BEGIN
    UPDATE CSM_RECORDS SET MIGRATION_STATE = 1, LAST_UPDATE = CURRENT_DATE
        WHERE OBJECT_UID IN 
        (SELECT OBJECT_UID 
        FROM CSM_RECORDS obj 
        WHERE MIGRATION_STATE = 0
        AND ROWNUM <= :BatchSize
        AND (:BatchName IS NULL OR obj.BATCH_NAME = :BatchName)
    RETURNING OBJECT_UID BULK COLLECT INTO t_Ids;
    
    OPEN :rcursor FOR SELECT * FROM CSM_RECORDS WHERE OBJECT_UID IN (SELECT * FROM TABLE(cast(t_Ids as id_array)));
    END;
    

    而 C# 看起来像:

    OracleCommand getNextNodesC = new OracleCommand(QUERY_DEFINED_ABOVE.Replace("\r\n", "\n"), conn);
    
    getNextNodesC.BindByName = true;
    
    OracleParameter batchSizeP = new OracleParameter("BatchSize", OracleDbType.Int32);
    batchSizeP.Value = batchSize;
    getNextNodesC.Parameters.Add(batchSizeP);
    
    OracleParameter batchNameP = new OracleParameter("BatchName", OracleDbType.Varchar2);
    batchNameP.Value = batchName;
    getNextNodesC.Parameters.Add(batchNameP);
    
    OracleParameter returnCursor = new OracleParameter("rcursor", OracleDbType.RefCursor);
    returnCursor.Direction = ParameterDirection.ReturnValue;
    getNextNodesC.Parameters.Add(returnCursor);
    
    return getNextNodesC.ExecuteReader();
    

    b_levitt 关于游标绑定变量上缺少冒号的建议是正确的。而且我还需要将查询中的所有“\r\n”替换为“\n”。

    我仍然不清楚为什么我需要运行CREATE TYPE... 而不仅仅是使用DECLARE TYPE...,因为后者似乎对块的BULK COLLECT 部分工作正常,但它现在似乎工作正常.

    再次感谢您的帮助。

    【讨论】:

      猜你喜欢
      • 2011-01-10
      • 1970-01-01
      • 2014-09-30
      • 2017-11-04
      • 1970-01-01
      • 1970-01-01
      • 2011-02-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多