从C#中的PL / SQL匿名块返回游标

时间:2014-04-02 19:25:21

标签: c# sql oracle plsql odp.net

我正在努力将现有的应用程序从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子句,试图让任何完全返回。

在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()的各种组合无效。

任何指针都会受到赞赏,但实际完成我正在寻找的代码的例子将是非常壮观的。

4 个答案:

答案 0 :(得分:3)

TLDR:你错过了光标绑定变量上的冒号:

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

完整答案: 您可能知道,在oracle中有pl / sql上下文(您的匿名块)和SQLplus上下文。这是一个带有SQLplus变量的块,带有相关绑定变量的块,以及最后的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

答案 1 :(得分:1)

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

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);
      }
    }
  }
}

答案 2 :(得分:0)

试试这个动态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;

答案 3 :(得分:0)

感谢大家的反馈。事实证明,在调试此问题时,我在某些时候切换了定义我的查询的变量,因此我在查询时所做的更改在运行应用程序时实际上并未应用。结果,我不确定究竟哪个修复得到了答案。但仅仅为了完整性,我最终使用的是有效的。

首先,运行:

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关于光标绑定变量上缺少冒号的建议虽然是现场开启的。我还需要用&#34; \ n&#34;替换查询中的所有&#34; \ r \ n&#34;

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

再次感谢您的帮助。