我想将一组id传递给将使用NHibernate映射的存储过程。这种技术是在Sql Server 2008中引入的(更多信息在这里=> Table-Valued Parameters)。我只是不想在nvarchar
参数中传递多个ID,然后在SQL Server端切断它的值。
答案 0 :(得分:31)
我的第一个临时想法是实现我自己的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,不是一个完整的解决方案,但它可以工作,并且在定制时可能很有用。如果有人知道更好/更短的解决方案,请告诉我们。
答案 1 :(得分:2)
您可以毫不费力地传递值集合。
示例:
var ids = new[] {1, 2, 3};
var query = session.CreateQuery("from Foo where id in (:ids)");
query.SetParameterList("ids", ids);
NHibernate将为每个元素创建一个参数。
答案 2 :(得分:2)
比接受的答案更简单的解决方案是使用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();
}
答案 3 :(得分:1)
对于我来说,我的存储过程需要在一个打开的事务中间调用。 如果存在未结事务,则此代码有效,因为它会自动重用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;
}
}