Как я понимаю, невозможно использовать параметры таблицы объектов Oracle (см. @ ответ Quassnoi ) с использованием nHibernate или ODP.NET. ODP.NET поддерживает только один тип коллекции: PLSQLAssociativeArray
.
Однако можно легко достичь того же результата, что и для TVP SQL Server, использующих ассоциативные массивы. Хитрость заключается в том, чтобы определить массив для каждого параметра вместо одного для всей таблицы.
Я публикую полное решение для проверки концепции, так как не смог найти его.
Схема Oracle
Схема включает в себя таблицу и упакованную процедуру вставки. Он обрабатывает каждый параметр как столбец и предполагает, что каждый массив имеет длину не менее первого.
create table test_table
(
foo number(9),
bar nvarchar2(64)
);
/
create or replace package test_package as
type number_array is table of number(9) index by pls_integer;
type nvarchar2_array is table of nvarchar2(64) index by pls_integer;
procedure test_proc(p_foo number_array, p_bar nvarchar2_array);
end test_package;
/
create or replace package body test_package as
procedure test_proc(p_foo number_array, p_bar nvarchar2_array) as
begin
forall i in p_foo.first .. p_foo.last
insert into test_table values (p_foo(i), p_bar(i));
end;
end test_package;
/
nHibernate Mapping
<sql-query name="test_proc">
begin test_package.test_proc(:foo, :bar); end;
</sql-query>
nHibernate Custom IType
Я заимствовал концепцию из отличного ответа, связанного с SQL Server , и немного изменил класс для работы с ODP.NET. Поскольку IType
огромен, я показываю только реализованные методы; остальные бросают NotImplementedException
.
Если кто-то захочет использовать это в рабочем коде, учтите, что я не тестировал этот класс всесторонне, даже если он делает то, что мне немедленно нужно.
public class OracleArrayType<T> : IType
{
private readonly OracleDbType _dbType;
public OracleArrayType(OracleDbType dbType)
{
_dbType = dbType;
}
public SqlType[] SqlTypes(IMapping mapping)
{
return new []{ new SqlType(DbType.Object) };
}
public bool IsCollectionType
{
get { return true; }
}
public int GetColumnSpan(IMapping mapping)
{
return 1;
}
public void NullSafeSet(IDbCommand st, object value, int index, ISessionImplementor session)
{
var s = st as OracleCommand;
var v = value as T[];
if (s != null && v != null)
{
s.Parameters[index].CollectionType = OracleCollectionType.PLSQLAssociativeArray;
s.Parameters[index].OracleDbType = _dbType;
s.Parameters[index].Value = value;
s.Parameters[index].Size = v.Length;
}
else
{
throw new NotImplementedException();
}
}
// IType boiler-plate implementation follows.
Параметр конструктора указывает тип базового типа массива (т. Е. Если вы передаете массив строк, передайте OracleDbType.NVarchar2
. Вероятно, есть способ вывести тип БД из типа значения, но я не уверен пока как это сделать.
Метод расширения для IQuery
Оборачивает создание типа:
public static class OracleExtensions
{
public static IQuery SetArray<T>(this IQuery query, string name, OracleDbType dbType, T[] value)
{
return query.SetParameter(name, value, new OracleArrayType<T>(dbType));
}
}
Использование
Чтобы связать все это вместе, вот как используется класс:
using (var sessionFactory = new Configuration().Configure().BuildSessionFactory())
using (var session = sessionFactory.OpenSession())
{
session
.GetNamedQuery("test_proc")
.SetArray("foo", OracleDbType.Int32, new[] { 11, 21 })
.SetArray("bar", OracleDbType.NVarchar2, new [] { "bar0", "bar1" })
.ExecuteUpdate();
}
Результат select * from test_table
после запуска кода:
FOO BAR
----------------
11 bar0
21 bar1