هل من الممكن استخدام `sqldbtype.structuregive` لتمرير المعلمات ذات القيمة التي تبلغ قيمة الجدول في nhibernate؟

StackOverflow https://stackoverflow.com/questions/3701364

سؤال

أرغب في تمرير مجموعة من المعرفات إلى إجراء مخزن سيتم تعيينه باستخدام 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;
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top