遅延エンティティに実際のインスタンスをロードさせる
-
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
およびセッションキャッシュを管理するその他の方法に関する情報は、 NHibernateドキュメントの第14.5章。
他のヒント
私の意見では、この問題を解決するのではなく、設計を再考する必要があります。この状況ではポリモーフィズムを使用できないことを絶対に確信しています-実行しようとしている操作を直接エンティティに責任させるか、ビジターパターンを使用します。私はこの問題に何度か出くわし、常に設計を変更することを決めました-それはより明確なコードをもたらしました。型に依存することが最善の解決策であることが確実でない限り、同じことを行うことをお勧めします。
問題
実世界に少なくともある程度類似した例を示すために、次のエンティティがあると仮定します。
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での解決策はltい-それは解決策でした。さて、あなたがあなたの問題にプロキシを導入したとき-それはもう機能していません。そのため、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メソッドの適切なオーバーロードが呼び出されます(コンパイラーは、
遅延読み込みを無効にすると、NHibernateプロキシの代わりに実際のインスタンスが強制的に返されます。
eg ..
mapping.Not.LazyLoad();
または
<class name="OrderLine" table="OrderLine" lazy="false" >
プロキシはエンティティクラスから派生しているため、おそらくentity.GetType()。BaseTypeをチェックして、定義済みのタイプを取得できます。