В SQL Server 2008 я могу передать параметр table-valued в мою хранимую процедуру из NHibernate. Как добиться того же в Oracle
Я создал таблицу как тип в SQL Server 2008.
Так как SQL Server 2008 поддерживает параметр передачи таблицы значений как параметр IN для хранимой процедуры. Он работает нормально.
Теперь я должен выполнить тот же подход в Oracle.
Я сделал это через PLSQLAssociativeArray
, но ограничение ассоциативного массива - они однородны (каждый элемент должен быть одного типа).
Где, как и в случае с табличным параметром SQL Server 2008, возможно.
Как добиться того же результата в Oracle.
Ниже приведены мой тип и хранимая процедура в SQL Server 2008:
CREATE TYPE [dbo].[EmployeeType] AS TABLE(
[EmployeeID] [int] NULL,
[EmployeeName] [nvarchar](50) NULL
)
GO
CREATE PROCEDURE [dbo].[TestCustom] @location EmployeeType READONLY
AS
insert into Employee (EMP_ID,EMP_NAME)
SELECT EmployeeID,EmployeeName
FROM @location;
GO
Вызов из NHibernate
var dt = new DataTable();
dt.Columns.Add("EmployeeID", typeof(int));
dt.Columns.Add("EmployeeName", typeof(string));
dt.Rows.Add(new object[] { 255066, "Nachi11" });
dt.Rows.Add(new object[] { 255067, "Nachi12" });
ISQLQuery final = eventhistorysession.CreateSQLQuery("Call TestCustom @pLocation = :id");
IQuery result = final.SetStructured("id", dt);
IList finalResult = result.List();
Ответы
Ответ 1
CREATE OR REPLACE TYPE employeeType AS OBJECT (employeeId INT, employeeName VARCHAR2(50));
CREATE TYPE ttEmployeeType AS TABLE OF employeeType;
CREATE PROCEDURE testCustom (pLocation ttEmployeeType)
AS
BEGIN
INSERT
INTO employee (emp_id, emp_name)
SELECT *
FROM TABLE(pLocation);
END;
Ответ 2
Как я понимаю, невозможно использовать параметры таблицы объектов Oracle (см. @Quassnoi answer для примера), используя либо nHibernate, либо ODP.NET. Единственный тип коллекции, поддерживаемый ODP.NET, - PLSQLAssociativeArray
.
Однако можно легко добиться того же результата, что и в SQL Server TVP, используя ассоциативные массивы. Трюк состоит в том, чтобы определить массив для каждого параметра вместо одного для всей таблицы.
Я публикую полное доказательство концепции, поскольку я не смог ее найти.
Схема 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