Можно ли использовать `SqlDbType.Structured` для передачи табличных значений в NHibernate?

Я хочу передать коллекцию идентификаторов в хранимую процедуру, которая будет отображаться с использованием NHibernate. Этот метод был представлен на Sql Server 2008 (подробнее здесь = > Табличные параметры). Я просто не хочу передавать несколько идентификаторов внутри параметра nvarchar, а затем нарезать его значение на стороне SQL Server.

Ответ 1

Моя первая, специальная идея заключалась в том, чтобы реализовать мой собственный IType.

public class Sql2008Structured : IType {
    private static readonly SqlType[] x = new[] { new SqlType(DbType.Object) };
    public SqlType[] SqlTypes(NHibernate.Engine.IMapping mapping) {
        return x;
    }

    public bool IsCollectionType {
        get { return true; }
    }

    public int GetColumnSpan(NHibernate.Engine.IMapping mapping) {
        return 1;
    }

    public void NullSafeSet(IDbCommand st, object value, int index, NHibernate.Engine.ISessionImplementor session) {
        var s = st as SqlCommand;
        if (s != null) {
            s.Parameters[index].SqlDbType = SqlDbType.Structured;
            s.Parameters[index].TypeName = "IntTable";
            s.Parameters[index].Value = value;
        }
        else {
            throw new NotImplementedException();
        }
    }

    #region IType Members...
    #region ICacheAssembler Members...
}

Больше не реализовано никаких методов; a throw new NotImplementedException(); - это все остальное. Затем я создал простое расширение для IQuery.

public static class StructuredExtensions {
    private static readonly Sql2008Structured structured = new Sql2008Structured();

    public static IQuery SetStructured(this IQuery query, string name, DataTable dt) {
        return query.SetParameter(name, dt, structured);
    }
}

Типичное использование для меня -

DataTable dt = ...;
ISession s = ...;
var l = s.CreateSQLQuery("EXEC some_sp @id = :id, @par1 = :par1")
            .SetStructured("id", dt)
            .SetParameter("par1", ...)
            .SetResultTransformer(Transformers.AliasToBean<SomeEntity>())
            .List<SomeEntity>();

Хорошо, но что такое "IntTable"? Это имя типа SQL, созданного для передачи аргументов значения таблицы.

CREATE TYPE IntTable AS TABLE
(
    ID INT
);

И some_sp может быть как

CREATE PROCEDURE some_sp
    @id IntTable READONLY,
    @par1 ...
AS
BEGIN
...
END

Он работает только с Sql Server 2008, конечно, и в этой конкретной реализации с помощью одного столбца DataTable.

var dt = new DataTable();
dt.Columns.Add("ID", typeof(int));

Это только POC, а не полное решение, но оно работает и может быть полезно при настройке. Если кто-то знает лучшее/более короткое решение, дайте нам знать.

Ответ 2

Вы можете передавать коллекции значений без хлопот.

Пример:

var ids = new[] {1, 2, 3};
var query = session.CreateQuery("from Foo where id in (:ids)");
query.SetParameterList("ids", ids);

NHibernate создаст параметр для каждого элемента.

Ответ 3

Более простым решением, чем принятый ответ, будет использование ADO.NET. NHibernate позволяет пользователям зачислять IDbCommands в транзакции NHibernate.

DataTable myIntsDataTable = new DataTable();
myIntsDataTable.Columns.Add("ID", typeof(int));

// ... Add rows to DataTable
ISession session = sessionFactory.GetSession();
using(ITransaction transaction = session.BeginTransaction())
{
    IDbCommand command = new SqlCommand("StoredProcedureName");
    command.Connection = session.Connection;
    command.CommandType = CommandType.StoredProcedure;
    var parameter = new SqlParameter();
    parameter.ParameterName = "IntTable";
    parameter.SqlDbType = SqlDbType.Structured;
    parameter.Value = myIntsDataTable;
    command.Parameters.Add(parameter);            
    session.Transaction.Enlist(command);
    command.ExecuteNonQuery();
}

Ответ 4

Приняли ли другие, чтобы эта реализация работала? Мне нужно передать несколько строк из нескольких столбцов (точнее, целых чисел), используя этот метод – Я определил свой базовый структурированный тип SQL и изменил ColumnSpan на возврат 5. Единственное другое отличие, которое я вижу, это то, что я не возвращаю никаких значений в выполнении моей хранимой процедуры. Он просто потребляет значения, предоставленные ему.

DataTable dt = ...;

var query = session.CreateSqlQuery("EXEC foo_proc_here @data = :data")
     .SetStructured("data", dt)
     .ExecuteUpdate();

К сожалению, это вызывает сообщение "Не удалось выполнить запрос массового манипулирования". Внутренние исключения жалуются на ошибки "Index Out of Range".

Я не совсем уверен, куда еще повернуть дальше, так как тема структурированных типов/параметров табличного значения с NHibernate кажется достаточно эзотерической, и я продолжаю заканчивать на этом же поступок.