我想将一组 id 传递给将使用 NHibernate 映射的存储过程。这种技术是在 Sql Server 2008 中引入的(更多信息在这里 =>表值参数)。我只是不想在一个nvarchar
参数中传递多个 id,然后在 SQL Server 端砍掉它的值。
4 回答
我的第一个临时想法是实现我自己的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(DbCommand 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();
其余的都是a 。接下来,我为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,不是一个完整的解决方案,但它可以工作并且在定制时可能很有用。如果有人知道更好/更短的解决方案,请告诉我们。
您可以轻松传递值的集合。
例子:
var ids = new[] {1, 2, 3};
var query = session.CreateQuery("from Foo where id in (:ids)");
query.SetParameterList("ids", ids);
NHibernate 将为每个元素创建一个参数。
比公认答案更简单的解决方案是使用 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();
}
就我而言,我的存储过程需要在打开的事务中间调用。如果有一个打开的事务,这段代码可以工作,因为它会自动重用 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;
}
}