NHibernateで正しいタイプのプロキシを取得する
-
03-07-2019 - |
質問
nhibernateの初期化されていないプロキシに問題があります
ドメインモデル
2つの並列クラス階層があるとします。Animal、Dog、Cat、AnimalOwner、DogOwner、CatOwnerで、DogとCatは両方ともAnimalを継承し、DogOwnerとCatOwnerはどちらもAnimalOwnerを継承します。 AnimalOwnerには、OwnedAnimalというAnimal型の参照があります。
例のクラスは次のとおりです。
public abstract class Animal
{
// some properties
}
public class Dog : Animal
{
// some more properties
}
public class Cat : Animal
{
// some more properties
}
public class AnimalOwner
{
public virtual Animal OwnedAnimal {get;set;}
// more properties...
}
public class DogOwner : AnimalOwner
{
// even more properties
}
public class CatOwner : AnimalOwner
{
// even more properties
}
クラスには適切なnhibernateマッピングがあり、すべてのプロパティは永続的で、遅延ロードできるものはすべて遅延ロードされます。
アプリケーションのビジネスロジックでは、DogOwnerでDogを設定し、CatOwnerでCatのみを設定できます。
問題
次のようなコードがあります:
public void ProcessDogOwner(DogOwner owner)
{
Dog dog = (Dog)owner.OwnedAnimal;
....
}
このメソッドは、多くの異なるメソッドで呼び出すことができます。ほとんどの場合、犬はすでにメモリ内にあり、すべて問題ありませんが、犬がメモリ内にないことはめったにありません-この場合、nhibernate" uninitialized proxy"ただし、nhibernateは犬ではなく動物のプロキシを生成するため、キャストは例外をスローします。
これがnhibernateの仕組みであることは理解していますが、オブジェクトをロードせずにタイプを知る必要があります。または、より正確には、初期化されていないプロキシが動物のプロキシではなく猫または犬のプロキシである必要があります
制約
- ドメインモデルを変更することはできません。別の部門からモデルが渡されました。モデルを変更させて失敗させました。
- 実際のモデルは例よりもはるかに複雑であり、クラス間に多くの参照があります。積極的な読み込みを使用したり、クエリに結合を追加したりすることはパフォーマンス上の理由で問題外です。
- ソースコード、hbmマッピング、およびデータベーススキーマを完全に制御し、必要に応じて変更できます(モデルクラス間の関係を変更しない限り)。
- 例にあるような多くのメソッドがあり、それらすべてを変更したくありません。
ありがとう、
ニル
解決
動物クラスの遅延読み込みをオフにするのが最も簡単です。とにかく主にメモリ内にあると言います。
<class name="Animal" lazy="false">
<!-- ... -->
</class>
その変形として、 no-proxy
を使用することもできます。この投稿:
<property name="OwnedAnimal" lazy="no-proxy"/>
見る限りでは、 AnimalOwner
が実際にプロキシである場合にのみ機能します。
または
動物の所有者でジェネリックを使用して、参照を具体的なクラスにすることができます。
class AnimalOwner<TAnimal>
{
virtual TAnimal OwnedAnimal {get;set;}
}
class CatOwner : AnimalOwner<Cat>
{
}
class DogOwner : AnimalOwner<Dog>
{
}
または
DogOwners
と CatOwners
を別々のテーブルにマッピングし、マッピングで具体的な動物の種類を定義できます。
<class name="CatOwner">
<!-- ... -->
<property name="OwnedAninal" class="Cat"/>
</class>
<class name="DogOwner">
<!-- ... -->
<property name="OwnedAninal" class="Dog"/>
</class>
または
thisで提案されているように、NHibernateを少しいじります。ブログ。 NHは、実際にはプロキシの背後にある実際のオブジェクトを返すことができます。ここで提案されているように、少し単純な実装:
public static T CastEntity<T>(this object entity) where T: class
{
var proxy = entity as INHibernateProxy;
if (proxy != null)
{
return proxy.HibernateLazyInitializer.GetImplementation() as T;
}
else
{
return entity as T;
}
}
次のように使用できます:
Dog dog = dogOwner.OwnedAnimal.CastEntit<Dog>();
他のヒント
最近、同様の問題が発生したと思います。AFAIRの解決策は、「動物」に自己を与えることでした-「メソッド/プロパティ」:
public Animal Self { get { return this; } }
これは、「動物」を修正するためにキャストできます。元のオブジェクトにはnhibernateプロキシオブジェクトへの参照があり(遅延ロードされた場合)、Animalクラスを介して公開されたすべてのメソッドの動物として機能します(ロードされたオブジェクトにすべての呼び出しを渡します)。ただし、他の動物としてキャストすることはできません。これらのいずれでもないため、Animalクラスをエミュレートするだけです。ただし、AnimalProxyによってカプセル化されたクラスは、正しいクラスの実際のインスタンスであるため、サブクラス化された動物としてキャストできます。参照する必要があるのは、 this
参照のみです。
このメソッドをベースエンティティに配置してみてください:
public virtual T As<T>() where T : Entity {
return this as T;
}
これを試して、プロキシされたタイプを確認することをお勧めします(NH 2.0+を想定):
((INHibernateProxy)proxy).HibernateLazyInitializer.PersistentClass
ただし、この種のキャストまたは「タイプピーク」とにかく非常に悪い習慣です...
同じ問題に取り組んでいる場合、問題は生成されたプロキシが犬ではなく動物のプロキシであるということです。
使用した解決策は、オブジェクトをリロードすることでした:
Dog dog = this.CurrentSession.Load<Dog>(owner.OwnedAnimal.AnimalID);
これはセッションに戻り、オブジェクトを正しいタイプでリロードします。
これが役立つことを願って
Fluent NHibernateを使用する場合、自動マッピングオーバーライドを使用して、そのプロパティのみの遅延読み込みをオフにできます。
public class DogOwnerMapOverride : IAutoMappingOverride<DogOwner>
{
public void Override( AutoMapping<DogOwner> mapping )
{
mapping.References( x => x.OwnedAnimal ).Not.LazyLoad();
}
}