هل من الممكن استخدام `sqldbtype.structuregive` لتمرير المعلمات ذات القيمة التي تبلغ قيمة الجدول في nhibernate؟
-
02-10-2019 - |
سؤال
أرغب في تمرير مجموعة من المعرفات إلى إجراء مخزن سيتم تعيينه باستخدام nhibernate. تم تقديم هذه التقنية في SQL Server 2008 (مزيد من المعلومات هنا => المعلمات ذات قيمة الجدول ). أنا فقط لا أريد تمرير معرفات متعددة داخل nvarchar
المعلمة ثم يقطع قيمتها على جانب خادم SQL.
المحلول
كانت الفكرة الأولى ، المخصصة ، هي تنفيذ بلدي 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 فقط ، وليس حلًا كاملاً ، لكنه يعمل وقد يكون مفيدًا عند تخصيصه. إذا كان شخص ما يعرف حلًا أفضل/أقصر ، فأخبرنا بذلك.
نصائح أخرى
حل أبسط من الإجابة المقبولة هو استخدام 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();
}
يمكنك تمرير مجموعات من القيم دون المتاعب.
مثال:
var ids = new[] {1, 2, 3};
var query = session.CreateQuery("from Foo where id in (:ids)");
query.SetParameterList("ids", ids);
سيقوم Nhibernate بإنشاء معلمة لكل عنصر.
بالنسبة لحالتي ، يجب استدعاء الإجراء المخزن في منتصف معاملة مفتوحة. إذا كانت هناك معاملة مفتوحة ، فإن هذا الرمز يعمل لأنه يعيد استخدام المعاملة الحالية تلقائيًا لجلسة Nhibernate:
NHibernateSession.GetNamedQuery("SaveStoredProc")
.SetInt64("spData", 500)
.ExecuteUpdate();
ومع ذلك ، بالنسبة لإجراءتي المخزنة الجديدة ، فإن المعلمة ليست بسيطة مثل Int64
. إنه معلمة ذات قيمة ذات قيمة (نوع الجدول المحدد للمستخدم) مشكلتي هي أنه لا يمكنني العثور على وظيفة المجموعة المناسبة. حاولت 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;
}
}