إجبار الكيان الكسول على تحميل المثيل الحقيقي

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

  •  05-07-2019
  •  | 
  •  

سؤال

لدي وكيل للكيان البطيء الذي تم إنشاؤه في الجلسة عن طريق تحميل كيان فرعي.يؤدي الجلب اللاحق للكيان الأصلي إلى إرجاع وكيل NH فقط.أحتاج إلى المثيل الفعلي للتحقق من النوع (انضم الكيان إلى الفئات الفرعية).لا بد أنني أفتقد شيئًا ما، لكن لا يمكنني العثور على طريقة للقيام بذلك.لا يبدو أن Session.Refresh(proxy) يساعد، ولا أي نكهة من HQL التي جربتها.

هل أستطيع مساعدتك؟

هل كانت مفيدة؟

المحلول

لفرض جلب وكيل من قاعدة البيانات، يمكنك استخدام الأمر NHibernateUtil.Initialize(proxy) الطريقة، أو الوصول إلى طريقة/خاصية الوكيل.

var foo = session.Get<Foo>(id);
NHibernateUtil.Initialize(foo.Bar);

للتحقق مما إذا كان قد تمت تهيئة الكائن أم لا، يمكنك استخدام NHibernateUtil.IsInitialized(proxy) طريقة.

تحديث:

لإزالة كائن من ذاكرة التخزين المؤقت للجلسة، استخدم الأمر Session.Evict(obj) طريقة.

session.Evict(myEntity);

معلومات عن Evict ويمكن العثور على طرق أخرى لإدارة ذاكرة التخزين المؤقت للجلسة في الفصل 14.5 من مستندات NHibernate.

نصائح أخرى

في رأيي، بدلا من حل هذه المشكلة، يجب عليك إعادة التفكير بدلا التصميم الخاص بك. هل أنت متأكد تماما، أنه لا يمكنك استخدام تعدد الأشكال في هذه الحالة - جعل إما مباشرة الكيان المسؤول عن العملية التي تحاول أداء أو استخدام نمط زائر. جئت عبر هذه المسألة عدة مرات، وقرر دائما لتغيير تصميم - أنها أدت في التعليمات البرمجية أكثر وضوحا. أقترح عليك أن تفعل الشيء نفسه، إلا إذا كنت متأكدا تماما من أن الاعتماد على نوع هو الحل الأفضل.

المشكلة

في أجل الحصول على سبيل المثال مع بعض على الأقل تشابه في العالم الحقيقي، دعونا نفترض أن لديك الكيانات التالية:

public abstract class Operation
{
    public virtual DateTime PerformedOn { get; set; }
    public virtual double Ammount { get; set; }
}

public class OutgoingTransfer : Operation
{
    public virtual string TargetAccount { get; set; }
}

public class AtmWithdrawal : Operation
{
    public virtual string AtmAddress { get; set; }
}

وانها تريد ان تكون بطبيعة الحال جزء صغير من نموذج أكبر من ذلك بكثير. والآن كنت تواجه مشكلة: لكل نوع ملموس من عملية، وهناك طريقة مختلفة لعرضه:

private static void PrintOperation(Operation operation)
{
    Console.WriteLine("{0} - {1}", operation.PerformedOn,
                      operation.Ammount);
}

private static void PrintOperation(OutgoingTransfer operation)
{
    Console.WriteLine("{0}: {1}, target account: {2}",
                      operation.PerformedOn, operation.Ammount,
                      operation.TargetAccount);
}

private static void PrintOperation(AtmWithdrawal operation)
{
    Console.WriteLine("{0}: {1}, atm's address: {2}",
                      operation.PerformedOn, operation.Ammount,
                      operation.AtmAddress);
}

وسوف بسيطة، وأساليب طاقتها العمل في حالة بسيطة:

var transfer = new OutgoingTransfer
               {
                   Ammount = -1000,
                   PerformedOn = DateTime.Now.Date,
                   TargetAccount = "123123123"
               };

var withdrawal = new AtmWithdrawal
                 {
                     Ammount = -1000,
                     PerformedOn = DateTime.Now.Date,
                     AtmAddress = "Some address"
                 };

// works as intended
PrintOperation(transfer);
PrintOperation(withdrawal);

ولسوء الحظ، لا بد طرق طاقتها في وقت الترجمة، لذلك بمجرد إدخال مجموعة / قائمة / مهما من العمليات، وسوف يطلق فقط عام (عملية التشغيل) الزائد.

Operation[] operations = { transfer, withdrawal };
foreach (var operation in operations)
{
    PrintOperation(operation);
}

وهناك نوعان من الحلول لهذه المشكلة، وكلاهما سلبيات. يمكنك إدخال وسيلة مجردة / الظاهري في عملية طباعة المعلومات لتيار المحدد. ولكن هذا سوف مزيج المخاوف UI إلى النموذج الخاص بك، لذلك هذا غير مقبول بالنسبة لك (أنا سأريك كيف يمكنك تحسين هذا الحل لتلبية توقعاتكم في لحظة).

ويمكنك أيضا إنشاء الكثير من المحاذير في شكل:

if(operation is (ConcreteType))
   PrintOperation((ConcreteType)operation);

وهذا الحل هو قبيح وعرضة للخطأ. في كل مرة إضافة / تغيير / إزالة النوع من العمليات، عليك أن تذهب من خلال كل مكان استخدمته هذه الإختراق وتعديله. وإذا كنت أفتقد مكان واحد، ربما عليك أن تكون قادرا على التقاط هذا وقت فقط - لا يتحقق وقت الترجمة صارمة لبعض الأخطاء (مثل عداد المفقودين سلالة واحدة)

وعلاوة على ذلك، سوف تفشل هذا الحل بمجرد إدخال أي نوع من الوكيل.

كيف يعمل بالوكالة

ورمز أدناه هو وكيل بسيط جدا (في هذا التطبيق أنه من نفس نمط الديكور - ولكن هذه الأنماط ليست هي نفسها في عام انها تريد ان تأخذ بعض تعليمات برمجية إضافية للتمييز بين هذه الأنماط اثنين.)

.
public class OperationProxy : Operation
{
    private readonly Operation m_innerOperation;

    public OperationProxy(Operation innerOperation)
    {
        if (innerOperation == null)
            throw new ArgumentNullException("innerOperation");
        m_innerOperation = innerOperation;
    }


    public override double Ammount
    {
        get { return m_innerOperation.Ammount; }
        set { m_innerOperation.Ammount = value; }
    }

    public override DateTime PerformedOn
    {
        get { return m_innerOperation.PerformedOn; }
        set { m_innerOperation.PerformedOn = value; }
    }
}

وكما ترون - هناك واحد فقط فئة وكيل التسلسل الهرمي كله. لماذا ا؟ لأنك يجب كتابة التعليمات البرمجية بطريقة لا تعتمد على نوع الخرسانة - فقط على قدم التجريد. هذا الوكيل يمكن تأجيل كيان تحميل في الوقت المناسب - وربما كنت لن تستخدم على الإطلاق؟ ربما عليك استخدام فقط 2 من 1000 الكيانات؟ لماذا تحميل كل منهم بعد ذلك؟

وهكذا NHibernate يستخدم الوكيل مثل على أعلى (أكثر تطورا، على الرغم من) تأجيل كيان التحميل. يمكن أن تخلق 1 وكيل لكل نوع فرعي، ولكن من شأنه أن يدمر الغرض كله من تحميل كسول. اذا نظرتم carefuly في كيفية مخازن NHibernate فرعية سترى، أنه من أجل تحديد ما هي كيان نوع هو، لديك لتحميله. ولذلك فمن المستحيل أن يكون وكلاء ملموسة - هل يمكن أن يكون أكثر مجردة فقط، OperationProxy

وألطاف الحل مع المؤسسة الدولية للعلوم انها قبيحة - كان الحل. الآن، عندما تعرفت كلاء لمشكلتك - انها لم تعد تعمل. ذلك أن مجرد يترك لنا مع أسلوب متعدد الأشكال، وهو أمر غير مقبول بسبب الاختلاط المسؤولية UI إلى النموذج الخاص بك. دعونا إصلاح ذلك.

انعكاس التبعية ونمط الزوار

أولا، دعونا نلقي نظرة على كيفية حل مع أساليب الافتراضية ستبدو (رمز بإضافته):

public abstract class Operation
{
    public abstract void PrintInformation();
}

public class OutgoingTransfer : Operation
{
    public override void PrintInformation()
    {
        Console.WriteLine("{0}: {1}, target account: {2}",
                      PerformedOn, Ammount, TargetAccount);
    }
}

public class AtmWithdrawal : Operation
{
    public override void PrintInformation()
    {
        Console.WriteLine("{0}: {1}, atm's address: {2}",
                          PerformedOn, Ammount, AtmAddress);
    }
}

public class OperationProxy : Operation
{
    public override void PrintInformation()
    {
        m_innerOperation.PrintInformation();
    }
}

والآن، عند استدعاء:

Operation[] operations = { transfer, withdrawal, proxy };
foreach (var operation in operations)
{
    operation.PrintInformation();
}

وكل يعمل السحر.

في أجل إزالة هذه التبعية واجهة المستخدم في النموذج، دعونا إنشاء واجهة:

public interface IOperationVisitor
{
    void Visit(AtmWithdrawal operation);
    void Visit(OutgoingTransfer operation);
}

ودعونا تعديل نموذج تعتمد على هذه الواجهة:

والآن إنشاء التنفيذ - ConsoleOutputOperationVisitor (لقد حذف طرق PrintInformation):

public abstract class Operation
{
    public abstract void Accept(IOperationVisitor visitor);
}

public class OutgoingTransfer : Operation
{
    public override void Accept(IOperationVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class AtmWithdrawal : Operation
{
    public override void Accept(IOperationVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class OperationProxy : Operation
{
    public override void Accept(IOperationVisitor visitor)
    {
        m_innerOperation.Accept(visitor);
    }
}

وماذا يحدث هنا؟ عند استدعاء قبول في عملية لالثانية تمرير الزوار، وسوف يطلق تنفيذ تقبل، حيث سيتم استدعاء الزائد المناسب لزيارة طريقة (مترجم يمكن تحديد نوع "هذا"). لذلك يمكنك الجمع بين "السلطة" وسائل الظاهري والزائدة للحصول على الطريقة المناسبة دعا. كما ترون - إشارة الآن UI هنا، نموذج يعتمد فقط على واجهة، والتي يمكن تضمينها في طبقة نموذج

وحتى الآن، للحصول على هذا العمل، وتنفيذ واجهة:

 public class ConsoleOutputOperationVisitor : IOperationVisitor
 {
    #region IOperationVisitor Members
    public void Visit(AtmWithdrawal operation)
    {
        Console.WriteLine("{0}: {1}, atm's address: {2}",
                          operation.PerformedOn, operation.Ammount,
                          operation.AtmAddress);
    }

    public void Visit(OutgoingTransfer operation)
    {
        Console.WriteLine("{0}: {1}, target account: {2}",
                          operation.PerformedOn, operation.Ammount,
                          operation.TargetAccount);
    }

    #endregion
}

وكود:

Operation[] operations = { transfer, withdrawal, proxy };
foreach (var operation in operations)
{
    operation.Accept(visitor);
}

وأنا أدرك تماما أن هذا ليس حلا مثاليا. عليك لا تزال بحاجة الى تعديل واجهة والزوار كما يمكنك إضافة أنواع جديدة. ولكن تحصل على وقت الترجمة فحص ولن تفوت أي شيء. الشيء الوحيد الذي سيكون من الصعب حقا لتحقيق باستخدام هذه الطريقة هو الحصول على أنواع فرعية للتوصيل - لكنني لست مقتنعا هذا هو السيناريو صحيح على أي حال. سيكون لديك أيضا لتعديل هذا النمط لتلبية الاحتياجات الخاصة بك في السيناريو ملموسة، ولكن سأترك لكم هذا.

وتعطيل تحميل كسول سيجبر المثال الفعلي يتم إرجاعها بدلا من وكيل NHibernate.

وعلى سبيل المثال ..

وmapping.Not.LazyLoad ()؛

أو

<class name="OrderLine" table="OrderLine" lazy="false" >

ومنذ مشتق الوكيل من الطبقة كيان، ربما يمكنك فقط تحقق entity.GetType (). BaseType للحصول على نوع الخاصة بك محددة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top