передача ассоциативного массива типа Timestamp в хранимую процедуру оракула

Мы сталкиваемся со странной ошибкой при использовании Oracle Odp.Net (подключение к Oracle 9). Проблема иллюстрируется приведенным ниже фрагментом кода.

Это ошибка, которую мы испытываем:

ORA-00600: код внутренней ошибки, аргументы: [15419], [серьезная ошибка во время выполнения PL/SQL], [], [], [], [], [], []

ORA-06544: PL/SQL: внутренняя ошибка, аргументы: [78502], [], [], [], [], [], [], []

ORA-06553: PLS-801: внутренняя ошибка [78502]

Поиск в Google заставляет нас подозревать (хотя мы не совсем уверены), что передача массива временных меток не поддерживается Odp.Net.

Так что вопрос двоякий:

  • можно ли передать массив меток времени в процедуру pl/sql с помощью odp.net?
  • если нет, есть ли хороший обходной путь?

Консольная программа C#, иллюстрирующая проблему:

using System;
using System.Collections;
using System.Data;
using Oracle.DataAccess.Client;

class Program 
{
private const string _db = "<db>";
private const string _username = "<user>";
private const string _password = "<password>";
private const string _storedProcedureName = "<sproc>";

static void Main(string[] args)
{
  var connectionString = string.Format(
                              "data source={0};user id={1};password={2}", 
                              _db, _username, _password);

  var connection = new OracleConnection(connectionString);

  try
  {

    connection.Open();


    var timeStamps = new[] { DateTime.Now, DateTime.Now };

    var parameter = new OracleParameter("inTimeStamps", OracleDbType.TimeStamp)
      {
        Direction = ParameterDirection.Input,
        CollectionType = OracleCollectionType.PLSQLAssociativeArray,
        Size = timeStamps.Length,
        Value = timeStamps
      };

    var command = connection.CreateCommand();
    command.CommandType = CommandType.StoredProcedure;
    command.CommandText = _storedProcedureName;
    command.Parameters.Add(parameter);

    command.ExecuteReader();

  }
  finally
  {
    connection.Close();
  }
}
}

Код вызывает следующую хранимую процедуру PL/SQL

  TYPE ArrayOfTimestamps is table of timestamp index by binary_integer;

  PROCEDURE TestOdpTimeStamp (inTimeStamps in ArrayOfTimestamps)
  IS
  test number;
  BEGIN
     select 1 into test from dual;
  END;

person jeroenh    schedule 12.10.2009    source источник


Ответы (3)


Вы можете передать вложенную таблицу временных меток вместо ассоциативного массива в процедуру PL/SQL.

Вам нужен odp.net 11.1.0.6.20 или выше, вы можете подключиться с odp.net 11.1.0.6.20 к серверу Oracle 9.

Выполнить тесты пользователя Oracle:

create or replace type MyTimeStamp as object 
(
  my timestamp
)
/

create or replace type mytimestamp_table as table of MyTimeStamp 
/

create table testinserttimestamp 
( my timestamp);

create or replace procedure test_timestamp_table (p_in in mytimestamp_table)
is
begin
  for i in p_in.first..p_in.last loop
    insert into testinserttimestamp values (p_in(i).my);
 end loop;
 commit;
end;

В С# создайте форму с кнопкой с именем button1 и выполните...

using System;
using System.Data;
using System.Windows.Forms;
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;

namespace TestTimeStamp
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }


    public class MyUdtTimeStamp : INullable, IOracleCustomType
    {

      [OracleObjectMappingAttribute("MY")]
      public OracleTimeStamp My { get; set; }

      public bool IsNull
      {
        get { return false;}
      }

      public void FromCustomObject(OracleConnection con, IntPtr pUdt)
      {
        OracleUdt.SetValue(con, pUdt, "MY", My);
      }

      public void ToCustomObject(OracleConnection con, IntPtr pUdt)
      {
        My = (OracleTimeStamp)OracleUdt.GetValue(con, pUdt, "MY");
      }
    }

    [OracleCustomTypeMappingAttribute("TESTTS.MYTIMESTAMP")]
    public class StudentFactory : IOracleCustomTypeFactory
    {
      public IOracleCustomType CreateObject()
      {
        return new MyUdtTimeStamp();
      }
    }

    [OracleCustomTypeMappingAttribute("TESTTS.MYTIMESTAMP_TABLE")]
    public class PersonArrayFactory : IOracleArrayTypeFactory
    {
      public Array CreateArray(int numElems)
      {
        return new MyUdtTimeStamp[numElems];
      }

      public Array CreateStatusArray(int numElems)
      {
        return null;
      }
    }

    private void button1_Click(object sender, EventArgs e)
    {
      OracleConnectionStringBuilder b = new OracleConnectionStringBuilder();
      b.UserID = "testts";
      b.Password = "ts";
      b.DataSource = "ora11";
      using (OracleConnection conn = new OracleConnection(b.ToString())) {
        conn.Open();
        using (OracleCommand comm = conn.CreateCommand())
        {
          comm.CommandText = "begin test_timestamp_table(:1); end;";
          OracleParameter p = new OracleParameter();
          p.OracleDbType = OracleDbType.Array;
          p.Direction = ParameterDirection.Input;

          p.UdtTypeName = "TESTTS.MYTIMESTAMP_TABLE";
          MyUdtTimeStamp[] times = new MyUdtTimeStamp[2];
          MyUdtTimeStamp m1 = new MyUdtTimeStamp();
          m1.My = new OracleTimeStamp(DateTime.Now);
          MyUdtTimeStamp m2 = new MyUdtTimeStamp();
          m2.My = new OracleTimeStamp(DateTime.Now);
          times[0] = m1;
          times[1] = m2;
          p.Value = times;

          comm.Parameters.Add(p);

          comm.ExecuteNonQuery();
        }

        conn.Close();
      }
    }
  }
}

Делать в Оракле...

SQL> select * from testinserttimestamp;

MY
-------------------------------------------------
12-10-09 21:13:54,328125
12-10-09 21:13:55,171875
person tuinstoel    schedule 12.10.2009
comment
Спасибо, выглядит хорошо. К сожалению, нам пока не разрешено обновляться до odp.net 11... - person jeroenh; 13.10.2009

Существует примечание Metalink (788282.1), в котором говорится, что это возможная ошибка из-за передачи неподдерживаемого типа данных. TIMESTAMP не поддерживается. Вы можете обойти это, создав анонимный блок PL/SQL в своем коде C# и вызвав проблематичную хранимую процедуру из этого блока.

РЕДАКТИРОВАТЬ:

Выложить код из Металинка не могу по понятным причинам.

Обходной путь проблематичен, если ваш массив содержит много значений, поскольку анонимный блок PL/SQL должен содержать код для явного присвоения каждой записи в массиве значения через переменную связывания. Это неуклюже. Это иллюстрирует идею:

comm.CommandText = "declare "+
                   "theTS mytimestamp_table;"+
                   "begin"+
                   "  theTS(1):= :1;"+
                   "  theTS(2):= :2;"+
                   "  test_timestamp_table(theTS);"+
                   "  end;"; 

Затем вам нужно будет создать список параметров, чтобы указать значения для каждой из переменных связывания.

person DCookie    schedule 12.10.2009
comment
Я не совсем уверен, что понимаю. Не могли бы вы привести небольшой пример того, что имеется в виду? Как передать параметр (в данном случае массив временных меток) анонимному блоку? - person jeroenh; 13.10.2009

Недавно я тоже столкнулся с этой проблемой, но решил ее по-другому, используя строки и to_timestamp. Надеюсь, это поможет всем, кто столкнется с этой проблемой в будущем: http://timscyclingblog.wordpress.com/2011/10/07/oracle-plsqlassociativearray-timestamp-workaround/

person Tim    schedule 07.10.2011