Можно ли использовать `SqlDbType.Structured` для передачи табличных параметров в NHibernate? - PullRequest
15 голосов
/ 13 сентября 2010

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

Ответы [ 4 ]

32 голосов
/ 27 сентября 2010

Моя первая, специальная идея заключалась в том, чтобы реализовать мою собственную 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...
}

Больше не реализовано никаких методов;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 голосов
/ 22 декабря 2015

Более простым решением, чем принятый ответ, было бы использование 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();
}
2 голосов
/ 13 сентября 2010

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

Пример:

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

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

1 голос
/ 15 февраля 2019

В моем случае моя хранимая процедура должна вызываться в середине открытой транзакции.Если есть открытая транзакция, этот код работает, потому что он автоматически повторно использует существующую транзакцию сеанса NHibernate:

NHibernateSession.GetNamedQuery("SaveStoredProc")
    .SetInt64("spData", 500)
    .ExecuteUpdate();

Однако для моей новой хранимой процедуры этот параметр не так прост, как Int64.Это параметр с табличным значением (определяемый пользователем тип таблицы) Моя проблема в том, что я не могу найти правильную функцию Set.Я попытался SetParameter("spData", tvpObj), но он возвращает эту ошибку:

Не удалось определить тип для класса:…

В любом случае, после некоторых проб и ошибок, этот подход нижепохоже на работу.Функция Enlist() является ключевым в этом подходе.Это в основном говорит SQLCommand использовать существующую транзакцию.Без этого произойдет ошибка, говорящая о том, что

ExecuteNonQuery требует, чтобы команда имела транзакцию, когда назначенное команде соединение находится в ожидающей локальной транзакции…

using (SqlCommand cmd = NHibernateSession.Connection.CreateCommand() as SqlCommand)
{
    cmd.CommandText = "MyStoredProc";
    NHibernateSession.Transaction.Enlist(cmd); // Because there is a pending transaction
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add(new SqlParameter("@wiData", SqlDbType.Structured) { Value = wiSnSqlList });
    int affected = cmd.ExecuteNonQuery();
}

Поскольку я использую класс SqlParameter с этим подходом, доступно SqlDbType.Structured.

Это функция, которой присваивается wiSnList:

private IEnumerable<SqlDataRecord> TransformWiSnListToSql(IList<SHWorkInstructionSnapshot> wiSnList)
{
    if (wiSnList == null)
    {
        yield break;
    }
    var schema = new[]
    {
        new SqlMetaData("OriginalId", SqlDbType.BigInt),           //0
        new SqlMetaData("ReportId", SqlDbType.BigInt),             //1
        new SqlMetaData("Description", SqlDbType.DateTime),        //2
    };

    SqlDataRecord row = new SqlDataRecord(schema);
    foreach (var wi in wiSnList)
    {
        row.SetSqlInt64(0, wi.OriginalId);
        row.SetSqlInt64(1, wi.ShiftHandoverReportId);
        if (wi.Description == null)
        {
            row.SetDBNull(2);
        }
        else
        {
            row.SetSqlString(2, wi.Description);
        }

        yield return row;
    }
}
...