在C#中从DBMS_OUTPUT.GET_LINES获取输出缓冲区

时间:2018-11-20 22:09:58

标签: c# .net oracle odp.net oracle-manageddataaccess

我试图通过C#从匿名PL / SQL块中的DBMS_OUTPUT.PUT_LINE()方法获取输出。我在这里查看了其他两个相关问题,但仍然遇到问题。执行匿名块的返回代码返回-1,基于docs应该是正确的。

我将DBMS_OUTPUT.ENABLE()设置为NULL以便不设置特定的缓冲区大小,然后使用DBMS_OUTPUT.GET_LINES()方法从该缓冲区中获取行。

它在缓冲区中不返回任何内容(空OracleString[])并返回0行。我的匿名PL / SQL块很简单,但应该适用于任何情况。

DECLARE
    lvsName VARCHAR2(6) := 'Oracle';
BEGIN
    DBMS_OUTPUT.PUT_LINE('Do you see me?');
    DBMS_OUTPUT.PUT_LINE('My name is: ' || lvsName);    
END;

我想念什么?

using (OracleDataAdapter oda = new OracleDataAdapter())
using (OracleCommand cmd = new OracleCommand(sql, _connection))
{
    // Execute anonymous PL/SQL block
    cmd.CommandType = CommandType.Text;
    var res = cmd.ExecuteNonQuery();

    // Set output Buffer
    cmd.CommandText = "BEGIN DBMS_OUTPUT.ENABLE(NULL); END;";
    cmd.CommandType = CommandType.Text;
    cmd.ExecuteNonQuery();

    // Get output
    cmd.CommandText = "BEGIN DBMS_OUTPUT.GET_LINES(:outString, :numLines); END;";
    cmd.CommandType = CommandType.Text;
    cmd.Parameters.Clear();
    cmd.Parameters.Add(new OracleParameter("outString", OracleDbType.Varchar2, int.MaxValue, ParameterDirection.Output));
    cmd.Parameters["outString"].CollectionType = OracleCollectionType.PLSQLAssociativeArray;
    cmd.Parameters["outString"].Size = sql.Length;
    cmd.Parameters["outString"].ArrayBindSize = new int[sql.Length];
    cmd.Parameters.Add(new OracleParameter("numLines", OracleDbType.Int32, ParameterDirection.InputOutput));
    cmd.Parameters["numLines"].Value = 10; // Get 10 lines
    cmd.ExecuteNonQuery();

     int numLines = Convert.ToInt32(cmd.Parameters["numLines"].Value.ToString());
     string outString = string.Empty;

     // Try to get more lines until there are zero left
     while (numLines > 0)
     {
         for (int i = 0; i < numLines; i++)
         {
             OracleString s = (OracleString)cmd.Parameters["outString"].Value;
             outString += s.ToString();
         }

         cmd.ExecuteNonQuery();
         numLines = Convert.ToInt32(cmd.Parameters["numLines"].Value.ToString());
     }

     return outString;
}

4 个答案:

答案 0 :(得分:4)

您的代码的主要问题是它没有为输出缓冲区的每个元素设置绑定大小。同样,在检索结果时,它没有正确索引输出缓冲区。最后,执行顺序也起着作用:您必须先启用输出,然后再执行匿名代码块。在以下MCVE中对所做的每一项更改进行注释。仅作了必要的更改以使其正常工作。

static void Main(string[] args)
{
    string str = "User Id=xxx; password=xxx; Data Source=localhost:1521/xxx;";
    string sql = @"DECLARE lvsName VARCHAR2(6) := 'Oracle'; BEGIN  DBMS_OUTPUT.PUT_LINE('Do you see me?'); DBMS_OUTPUT.PUT_LINE('My name is: ' || lvsName); END;";

    OracleConnection _connection = new OracleConnection(str);

    try
    {
        _connection.Open();

        //adapter not being used
        //using (OracleDataAdapter oda = new OracleDataAdapter())

        using (OracleCommand cmd = new OracleCommand(sql, _connection))
        {
            // First enable buffer output
            // Set output Buffer
            cmd.CommandText = "BEGIN DBMS_OUTPUT.ENABLE(NULL); END;";
            cmd.CommandType = CommandType.Text;
            cmd.ExecuteNonQuery();

            // Then execute anonymous block
            // Execute anonymous PL/SQL block
            cmd.CommandText = sql;
            cmd.CommandType = CommandType.Text;
            var res = cmd.ExecuteNonQuery();


            // Get output
            cmd.CommandText = "BEGIN DBMS_OUTPUT.GET_LINES(:outString, :numLines); END;";
            cmd.CommandType = CommandType.Text;

            cmd.Parameters.Clear();

            cmd.Parameters.Add(new OracleParameter("outString", OracleDbType.Varchar2, int.MaxValue, ParameterDirection.Output));
            cmd.Parameters["outString"].CollectionType = OracleCollectionType.PLSQLAssociativeArray;
            cmd.Parameters["outString"].Size = sql.Length;
            cmd.Parameters["outString"].ArrayBindSize = new int[sql.Length];

            // set bind size for each array element
            for (int i = 0; i < sql.Length; i++)
            {
                cmd.Parameters["outString"].ArrayBindSize[i] = 32000;
            }


            cmd.Parameters.Add(new OracleParameter("numLines", OracleDbType.Int32, ParameterDirection.InputOutput));
            cmd.Parameters["numLines"].Value = 10; // Get 10 lines
            cmd.ExecuteNonQuery();

            int numLines = Convert.ToInt32(cmd.Parameters["numLines"].Value.ToString());
            string outString = string.Empty;

            // Try to get more lines until there are zero left
            while (numLines > 0)
            {
                for (int i = 0; i < numLines; i++)
                {
                    // use proper indexing here
                    //OracleString s = (OracleString)cmd.Parameters["outString"].Value;
                    OracleString s = ((OracleString[])cmd.Parameters["outString"].Value)[i];
                    outString += s.ToString();

                    // add new line just for formatting
                    outString += "\r\n";
                }

                cmd.ExecuteNonQuery();
                numLines = Convert.ToInt32(cmd.Parameters["numLines"].Value.ToString());
            }

            Console.WriteLine(outString);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }

    _connection.Close();
    _connection.Dispose();

    Console.WriteLine("Press RETURN to exit.");
    Console.ReadLine();
}

输出结果是:

Do you see me?
My name is: Oracle

Press RETURN to exit.

答案 1 :(得分:2)

在我看来您做错了顺序...

// Execute anonymous PL/SQL block
cmd.CommandType = CommandType.Text;
var res = cmd.ExecuteNonQuery();

// Set output Buffer
cmd.CommandText = "BEGIN DBMS_OUTPUT.ENABLE(NULL); END;";
cmd.CommandType = CommandType.Text;
cmd.ExecuteNonQuery();

// Get output
cmd.CommandText = "BEGIN DBMS_OUTPUT.GET_LINES(:outString, :numLines); END;";

DBMS_OUTPUT设置(启用)和使用GET_LINES获取输出之间应该是您的写命令,但是,这是您要执行的第一件事。

尝试更改顺序。让我知道它是否有效,因为我没有尝试过(我不习惯C#...我在Java中拥有它)。

答案 2 :(得分:1)

我不会讲C#,但是在您的代码中看不到您正在为numLines变量赋值的地方。

  DBMS_OUTPUT.GET_LINES (
   lines       OUT     CHARARR,
   numlines    IN OUT  INTEGER);

plsql中的示例:

DECLARE
   v_array       DBMS_OUTPUT.CHARARR;
   v_lines   NUMBER;
BEGIN
   DBMS_OUTPUT.PUT_LINE ('aaaaa');
   DBMS_OUTPUT.put_line ('bbbb');
   DBMS_OUTPUT.put_line ('ccccc');
   v_lines := 1000; -- Number of lines you want to retrieve from the buffer.  
   DBMS_OUTPUT.GET_LINES (v_array, v_lines);

   DBMS_OUTPUT.put_line(v_lines); -- Lines retrieved from buffer.
   FOR idx IN nvl(v_array.FIRST,1) .. nvl(v_array.LAST,-1)
   LOOP
      DBMS_OUTPUT.put_line (v_array (idx));
   END LOOP;
END;

答案 3 :(得分:1)

感谢jsanalytics的上述回答,这为解决方案提供了良好的基础。但是,上述解决方案存在一些问题,大部分与sql.Length的用法有关,在许多没有意义的地方。这是一个可重用的解决方案,可以纠正一些问题。

using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace MyNamespace
{
    public static class DbmsOutputHelper
    {
        public const int DefaultReadBatchSize = 10;

        public static void EnableDbmsOutput(this OracleConnection conn)
        {
            using (var cmd = conn.CreateCommand())
            {
                cmd.CommandText = "DBMS_OUTPUT.ENABLE";
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.ExecuteNonQuery();
            }
        }

        public static void DisableDbmsOutput(this OracleConnection conn)
        {
            using (var cmd = conn.CreateCommand())
            {
                cmd.CommandText = "DBMS_OUTPUT.DISABLE";
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.ExecuteNonQuery();
            }
        }

        public static List<string> ReadDbmsOutput(this OracleConnection conn, int readBatchSize = DefaultReadBatchSize)
        {
            if (readBatchSize <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(readBatchSize), "must be greater than zero");
            }

            using (var cmd = conn.CreateCommand())
            {
                cmd.CommandText = "DBMS_OUTPUT.GET_LINES";
                cmd.CommandType = CommandType.StoredProcedure;

                var linesParam = cmd.Parameters.Add(new OracleParameter("lines", OracleDbType.Varchar2, int.MaxValue, ParameterDirection.Output));
                linesParam.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
                linesParam.Size = readBatchSize;
                linesParam.ArrayBindSize = Enumerable.Repeat(32767, readBatchSize).ToArray();   // set bind size for each array element

                var numLinesParam = cmd.Parameters.Add(new OracleParameter("numlines", OracleDbType.Int32, ParameterDirection.InputOutput));

                var result = new List<string>();
                int numLinesRead;

                do
                {
                    numLinesParam.Value = readBatchSize;
                    cmd.ExecuteNonQuery();
                    numLinesRead = ((OracleDecimal)numLinesParam.Value).ToInt32();

                    var values = (OracleString[])linesParam.Value;

                    for (int i = 0; i < numLinesRead; i++)
                    {
                        result.Add(values[i].ToString());
                    }

                } while (numLinesRead == readBatchSize);

                return result;
            }
        }
    }
}