8

子エンティティをロードすることによってセッションで作成されたレイジーエンティティのプロキシがあります。親エンティティでの後続のフェッチは、NHプロキシのみを返します。タイプを確認するために実際のインスタンスが必要です(エンティティはサブクラスに参加しています)。私は何かが欠けているに違いありませんが、これを行う方法を見つけることができません。Session.Refresh(proxy)は役に立たないようで、私が試したHQLのフレーバーも役に立ちません。

誰か助けてもらえますか?

4

4 に答える 4

21

私の意見では、この問題を解決するのではなく、設計を再考する必要があります。この状況ではポリモーフィズムを使用できないことを絶対に確信していますか?実行しようとしている操作をエンティティに直接任せるか、ビジターパターンを使用してください。私はこの問題に数回遭遇し、常にデザインを変更することに決めました。その結果、コードがより明確になりました。型に依存することが最善の解決策であると確信していない限り、同じことを行うことをお勧めします。

問題

実世界に少なくともある程度似た例を示すために、次のエンティティがあるとします。

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);
}

この問題には 2 つの解決策があり、どちらにも欠点があります。Operation に抽象/仮想メソッドを導入して、選択したストリームに情報を出力できます。しかし、これでは UI の問題がモデルに混入することになるため、これは受け入れられません (すぐに、このソリューションを改善して期待に応える方法を説明します)。

次の形式で多数の if を作成することもできます。

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

このソリューションは見苦しく、エラーが発生しやすいものです。操作の種類を追加/変更/削除するたびに、これらのハックを使用したすべての場所を調べて変更する必要があります。また、1 つの場所を見逃した場合、おそらくそのランタイムしかキャッチできません。一部のエラー (サブタイプが 1 つ欠落しているなど) に対する厳密なコンパイル時のチェックは行われません。

さらに、このソリューションは、何らかの種類のプロキシを導入するとすぐに失敗します.

プロキシの仕組み

以下のコードは非常に単純なプロキシです (この実装ではデコレータ パターンと同じですが、これらのパターンは一般的に同じではありません。これら 2 つのパターンを区別するには、追加のコードが必要です)。

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; }
    }
}

ご覧のとおり、階層全体に対してプロキシ クラスは 1 つしかありません。なんで?具体的な型に依存しない方法でコードを記述する必要があるため、提供された抽象化のみに依存します。このプロキシは、エンティティの読み込みを遅らせることができます。おそらく、まったく使用しないでしょうか? たぶん、1000 個のエンティティのうち 2 個だけを使用するでしょうか? なぜそれらすべてをロードするのですか?

そのため、NHibernate は上記のようなプロキシを使用して (ただし、はるかに洗練されています)、エンティティの読み込みを遅らせます。サブタイプごとに 1 つのプロキシを作成できますが、遅延読み込みの目的全体が破壊されます。NHibernate がサブクラスを格納する方法を注意深く見ると、エンティティの型を判断するには、それをロードする必要があることがわかります。したがって、具体的なプロキシを持つことは不可能です。最も抽象的な OperationProxy しか持つことができません。

ifs を使用したソリューションは醜いですが、それはソリューションでした。さて、問題にプロキシを導入すると、もはや機能しません。そのため、モデルに 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();
}

すべてがチャームとして機能します。

モデルでこの UI の依存関係を削除するために、インターフェイスを作成しましょう。

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);
    }
}

そこで何が起こるの?操作で Accept を呼び出してビジターを渡すと、Accept の実装が呼び出され、Visit メソッドの適切なオーバーロードが呼び出されます (コンパイラーは「this」の型を判別できます)。したがって、仮想メソッドとオーバーロードの「力」を組み合わせて、適切なメソッドを呼び出すことができます。ご覧のとおり、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);
}

これが完璧な解決策ではないことは十分承知しています。新しいタイプを追加するときは、インターフェイスと訪問者を変更する必要があります。ただし、コンパイル時のチェックが行われるため、何も見逃すことはありません。この方法を使用して実現するのが非常に難しいことの 1 つは、プラグ可能なサブタイプを取得することですが、とにかくこれが有効なシナリオであるとは確信していません。また、具体的なシナリオのニーズに合わせてこのパターンを変更する必要がありますが、これはお任せします。

于 2009-07-30T07:37:43.310 に答える
12

データベースからプロキシを強制的に取得するには、NHibernateUtil.Initialize(proxy)メソッドを使用するか、プロキシのメソッド/プロパティにアクセスします。

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

オブジェクトが初期化されているかどうかを確認するには、NHibernateUtil.IsInitialized(proxy)メソッドを使用できます。

アップデート:

セッション キャッシュからオブジェクトを削除するには、Session.Evict(obj)メソッドを使用します。

session.Evict(myEntity);

セッション キャッシュを管理するための情報Evictやその他の方法については、NHibernate ドキュメントの14.5 章を参照してください。

于 2009-07-30T07:31:45.427 に答える
3

遅延読み込みを無効にすると、NHibernate Proxy の代わりに実際のインスタンスが強制的に返されます。

例えば..

mapping.Not.LazyLoad();

また

<class name="OrderLine" table="OrderLine" lazy="false" >
于 2011-03-13T21:11:03.387 に答える
-2

プロキシはエンティティ クラスから派生しているため、おそらく entity.GetType().BaseType をチェックして定義済みの型を取得できます。

于 2009-07-30T03:31:22.240 に答える