【问题标题】:Call Oracle stored procedure with a nested table from .NET使用 .NET 中的嵌套表调用 Oracle 存储过程
【发布时间】:2013-01-21 23:38:15
【问题描述】:

给定下表:

create table egyen (
  id number not null, 
  tajszam varchar2(9),
  nev varchar2(10),
  primary key(id)
);

还有以下包:

create or replace package pck_egyen is
  type egyentab is table of egyen%rowtype;
  procedure list(ret$ out egyentab);
end pck_egyen;
/
create or replace package body pck_egyen is
  procedure list(ret$ out egyentab) is
  begin
    ret$ := egyentab();
    for rec in (select * from egyen) loop
      ret$.extend;
      ret$(ret$.count) := rec;
    end loop;
  end;
end pck_egyen;
/

有人可以为我提供一个使用 ODP.NET 11g 的工作 .NET 代码,它可以调用此存储过程,而无需以任何方式更改表或类型或包?我不知道如何设置 OracleParameter 来支持 pck_egyen.egyentab 类型...

我之前没有使用过UDT,我试了一下,但它无法识别包中嵌入的类型。全局类型不支持 %rowtype。好像我被卡住了。最初我想返回 ref 游标,就像一个魅力,但不是通过 dblink。太多愚蠢的限制。

【问题讨论】:

  • 为什么不以任何方式更改包/过程的限制? rowtype 是一个 pl/sql 构造,并且不使用全局对象类型,不确定您希望 .NET 如何理解如何映射到 egyentab。跨 dblinks 使用表函数有一些变通方法,但坚持在这里没有任何改变可能最终不会得到最好的结果。
  • 我必须使用存储过程跨 dblink 传输表行。 Refcursor 不能跨 dblink 工作。有数百个表和过程,所以没有 %rowtype,声明表类型是太多额外的工作。如果Oracle真的不支持调用这个过程,那会很痛苦,这是肯定的。
  • 我想我要问的是你在这里的最终目标是什么?如果您必须跨 dblink 传输行,为什么还需要 .NET? (似乎是这样的:.NET 创建空对象 -> 调用 proc@db_A 填充 -> .NET -> 插入 db_B )。这是正确的吗?
  • .NET 和 dblink 不相关。我需要一个可以从.NET 调用的SP,也可以通过dblink 从其他存储过程调用。看起来嵌套表是我可以在 Oracle 中管理 dblink 的唯一方法,所以剩下的任务是从 .NET 调用相同的东西,因此是我的问题。

标签: .net oracle stored-procedures


【解决方案1】:

试试类似的东西

OracleCommand cmd = new OracleCommand();
cmd.CommandText = "pck_egyen.list";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = con;
cmd.Parameters.Add("ret$", OracleDbType.RefCursor, DBNull.Value, ParameterDirection.Output);

cmd.ExecuteNonQuery();

// Read the result set
OracleRefCursor orarefcur = (OracleRefCursor) cmd.Parameters[0].Value;
OracleDataReader dr = orarefcur.GetDataReader();    
while (dr.Read())
{
    System.Console.WriteLine(dr["tajszam"]);
}

【讨论】:

  • 行不通,它不是参考光标。 PLS-00306:调用“LIST”时参数的数量或类型错误
【解决方案2】:

一种方法是使用一些 PL/SQL 将表对象中的数据读取到 PL/SQL 关联数组中,每列一个,然后使用 ODP.NET 读取这些数据:

    private const string PlSqlBlock = @"
        DECLARE
          l_egyen_tab  pck_egyen.egyentab;
        BEGIN
          pck_egyen.list(l_egyen_tab);
          FOR i IN 1..l_egyen_tab.COUNT
          LOOP
            :ids(i) := l_egyen_tab(i).id;
            :tajszams(i) := l_egyen_tab(i).tajszam;
            :nevs(i) := l_egyen_tab(i).nev;
          END LOOP;
        END;";

    public static void ListEgyenTable(OracleConnection con)
    {
        using (var cmd = new OracleCommand(PlSqlBlock, con))
        {
            OracleParameter idParam = cmd.Parameters.Add("ids", OracleDbType.Decimal);
            OracleParameter tajszamParam = cmd.Parameters.Add("tajszams", OracleDbType.Varchar2);
            OracleParameter nevParam = cmd.Parameters.Add("nevs", OracleDbType.Varchar2);

            int arraySize = 1000;
            int[] varcharArrayBindSize = Enumerable.Repeat(4000, arraySize).ToArray();

            foreach (OracleParameter param in cmd.Parameters)
            {
                param.Direction = ParameterDirection.Output;
                param.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
                param.Size = arraySize;
                if (param.OracleDbType == OracleDbType.Varchar2)
                {
                    param.ArrayBindSize = varcharArrayBindSize;
                }
            }

            cmd.ExecuteNonQuery();

            if (idParam.Value is OracleDecimal[] && tajszamParam.Value is OracleString[] && nevParam.Value is OracleString[])
            {
                List<decimal> ids = (idParam.Value as OracleDecimal[]).Select(dec => dec.Value).ToList();
                List<string> tajszams = (tajszamParam.Value as OracleString[]).Select(str => str.Value).ToList();
                List<string> nevs = (nevParam.Value as OracleString[]).Select(str => str.Value).ToList();

                for (int i = 0; i < ids.Count; ++i)
                {
                    Console.WriteLine("Got id {0}, tajszam {1}, nev {2}", ids[i], tajszams[i], nevs[i]);
                }
            }
            else
            {
                Console.WriteLine("Sorry, returned data not as expected :(");
            }
        }
    }

这里唯一的复杂之处是arraySize。该值至少需要从存储过程返回的行数,如果太小,您将得到 ORA-06513 'PL/SQL: index for PL/SQL table out of range for host language数组错误。

我创建了你的表和包,并在表中插入了以下测试数据:

SQL> select * from egyen;

        ID TAJSZAM   NEV
---------- --------- ----------
         1 abc       defg
         2 def       mnop
         3 ghi       qrstu
         4 jkl       vwxyz

当我运行上面的 C# 代码时,我得到了以下输出:

得到 id 1, tajszam abc, nev defg 得到 id 2,tajszam def,nev mnop Got id 3, tajszam ghi, nev qrstu 得到 id 4, tajszam jkl, nev vwxyz

【讨论】:

  • 这很疯狂,但它确实有效。我对性能(100 多列,多行)和数组大小限制有疑问。这给了我一个想法:创建一个包装 plsql 块,它返回游标中的内容,但它给了我 ORA-21700 :(。所以你的解决方案仍然是迄今为止最好的。
【解决方案3】:

这是一个有趣的问题,特别是因为需要同时使用 .NET 来跨 dblink 使用数据和存储过程。不跨 dblinks 使用游标的限制导致您使用 pl/sql 表,但您不能在 .NET 中轻松使用这些表(无需经历设置和维护全局对象的痛苦)。

因此,我建议同时使用引用游标函数和带有输出表参数的过程。您将能够轻松地从 .NET 调用 ref 游标函数(您不需要跨 dblink 进行选择),并且对于 dblink 数据库工作,使用相应的过程。例如:

create table test1 (
    col1 number,
    col2 varchar2(10),
    col3 date default sysdate not null
);
insert into test1(col1,col2) values (1,'A');
insert into test1(col1,col2) values (1,'X');
insert into test1(col1,col2) values (2,'B');

commit;

CREATE OR REPLACE package TEST_PKG as
  type t_test1_tab is table of test1%rowtype;
  -- weak ref cursor
  function get_test1_cur (i_num in number) return sys_refcursor;
  -- uses rowtype for table
  procedure get_test1_tab(i_num in number, o_tab out t_test1_tab);
end;


CREATE OR REPLACE package body TEST_PKG as
  function get_test1_cur (i_num in number) return sys_refcursor is
    l_cur sys_refcursor;
  begin
    open l_cur for select * from test1 where col1=i_num;
    return l_cur;
  end;

  procedure get_test1_tab(i_num in number, o_tab out t_test1_tab) is
    l_rec test1%rowtype;
    l_tab t_test1_tab := t_test1_tab();
    l_cur sys_refcursor;
  begin
    l_cur := get_test1_cur(i_num);
    loop
      fetch l_cur into l_rec;
      exit when l_cur%notfound;
      l_tab.extend;
      l_tab(l_tab.last) := l_rec;
    end loop;
    close l_cur;
    o_tab := l_tab;
  end;

end;

将所需的任何逻辑放入 ref 游标函数中。该过程只是调用函数并创建表(使用行类型)。

使用跨 dblink 调用 db 的过程:

declare
  l_tab test_pkg.t_test1_tab@dblinkA;
begin
  test_pkg.get_test1_tab@dblinkA(1, l_tab);
  -- show count
  dbms_output.put_line('Table has ' || l_tab.count || ' rows.');
end;

使用该函数进行 odp.net 调用。例如(代码sn-p):

OracleConnection con = new OracleConnection(connStr);

// create the command object and set attributes
OracleCommand cmd = new OracleCommand("test_pkg.get_test1_cur", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.BindByName = false;

// create parameter object for the cursor
OracleParameter p_refcursor = new OracleParameter();
// create any input parameters to the function
OracleParameter p_num = new OracleParameter();

p_refcursor.OracleDbType = OracleDbType.RefCursor;
p_refcursor.Direction = ParameterDirection.ReturnValue;
cmd.Parameters.Add(p_refcursor);

// add any input parameters
p_num.OracleDbType = OracleDbType.Int32;
p_num.Direction = ParameterDirection.Input;
p_num.Value = 1;
cmd.Parameters.Add(p_num);

// create a data adapter to use with the data set
OracleDataAdapter da = new OracleDataAdapter(cmd);

// create the data set
DataSet ds = new DataSet();

// fill the data set
da.Fill(ds);

这种方法应该易于维护,因为您不仅使用的是行类型,而且过程只是调用函数。

【讨论】:

    猜你喜欢
    • 2017-05-25
    • 2014-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-13
    • 2011-09-28
    相关资源
    最近更新 更多