Передача ассоциативного массива типа Timestamp в хранимую процедуру Oracle
Мы сталкиваемся со странной ошибкой при использовании Oracle Odp.Net (подключение к Oracle 9). Проблема иллюстрируется фрагментом кода ниже.
Это ошибка, с которой мы сталкиваемся:
ORA-00600: внутренний код ошибки, аргументы: [15419], [серьезная ошибка при выполнении PL/SQL], [], [], [], [], [], []
ORA-06544: PL/SQL: внутренняя ошибка, аргументы: [78502], [], [], [], [], [], [], []
ORA-06553: PLS-801: внутренняя ошибка [78502]
Поиск в Google заставляет нас подозревать (хотя мы не совсем уверены), что передача массива временных меток не поддерживается Odp.Net.
Итак, вопрос в 2 раза:
- Можно ли передать массив меток времени в процедуру 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;
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;
В C# создайте форму с кнопкой 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();
}
}
}
}
Делай в Oracle...
SQL> select * from testinserttimestamp;
MY
-------------------------------------------------
12-10-09 21:13:54,328125
12-10-09 21:13:55,171875
Недавно я тоже столкнулся с этой проблемой, но я преодолеваю по-другому, используя строки и to_timestamp, я надеюсь, что это поможет любому, кто пойдет дальше, столкнется с этой проблемой: http://timscyclingblog.wordpress.com/2011/10/07/oracle-plsqlassociativearray-timestamp-workaround/
Существует примечание Metalink (788282.1), в котором говорится, что возможна ошибка при передаче неподдерживаемого типа данных. TIMESTAMP не поддерживается. Вы можете обойти это, создав анонимный PL/SQL-блок в своем коде C# и вызвав проблемную хранимую процедуру из этого блока.
РЕДАКТИРОВАТЬ:
Я не могу опубликовать код от Metalink по понятным причинам.
Обходной путь проблематичен, если ваш массив содержит много значений, поскольку анонимный блок PL/SQL должен будет содержать код для явного присвоения каждой записи в массиве значения через переменную связывания. Это неуклюже Это иллюстрирует идею:
comm.CommandText = "declare "+
"theTS mytimestamp_table;"+
"begin"+
" theTS(1):= :1;"+
" theTS(2):= :2;"+
" test_timestamp_table(theTS);"+
" end;";
Затем вам нужно будет создать свой список параметров, чтобы обеспечить значения для каждой из переменных связывания.