Подтвердить что ты не робот

Получение прокси нужного типа в NHibernate

У меня проблема с неинициализированными прокси в nhibernate

Модель домена

Скажем, у меня есть две параллельные иерархии классов: Animal, Dog, Cat и AnimalOwner, DogOwner, CatOwner, где Dog и Cat наследуют от Animal и DogOwner и CatOwner как наследуемые от AnimalOwner. У AnimalOwner есть ссылка типа Animal под названием OwnedAnimal.

Вот классы в примере:

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 и Cat в CatOwner.

Проблема

У меня такой код:

public void ProcessDogOwner(DogOwner owner)
{
   Dog dog = (Dog)owner.OwnedAnimal;
   ....
}

Этот метод может быть вызван многими различными способами, в большинстве случаев собака уже находится в памяти, и все в порядке, но редко собака еще не в памяти - в этом случае я получаю nhibernate "неинициализированный прокси", но cast бросает исключение, потому что nhibernate кодирует прокси для Animal, а не для Dog.

Я понимаю, что так работает nhibernate, но мне нужно знать тип без загрузки объекта - или, вернее, мне нужен неинициализированный прокси-сервер для прокси-сервера Cat или Dog, а не для прокси-сервера Animal.

Ограничения

  • Я не могу изменить модель домена, модель передана мне другим отделом, я попытался заставить их изменить модель и не удалось.
  • Фактическая модель намного сложнее, чем пример, и классы имеют много ссылок между ними, используя загрузку или добавление объединений к запросам, не может быть и речи по причинам производительности.
  • У меня есть полный контроль над исходным кодом, отображением hbm и схемой базы данных, и я могу их изменить любым способом (до тех пор, пока не меняю отношения между классами модели).
  • У меня много методов, как в примере, и я не хочу изменять их все.

Спасибо,
Nir

4b9b3361

Ответ 1

Легче всего отключить ленивую загрузку для класса животных. В любом случае вы говорите это в основном в памяти.

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

ИЛИ

Вы немного помешаны в 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>();

Ответ 2

Я думаю, что у нас недавно была аналогичная проблема, решение AFAIR состояло в том, чтобы дать "Animal" свой собственный метод/свойство:

public Animal Self { get { return this; } }

Затем это можно было бы отбросить, чтобы исправить "животное". Случается, что ваш исходный объект имеет ссылку на прокси-объект nhibernate (когда он лениво загружен), который действует как Animal для всех методов, открытых через класс Animal (он передает все вызовы загруженному объекту). Однако он не может быть брошен как любой из ваших других животных, потому что он ни один из них, он только эмулирует класс животных. Однако класс, который инкапсулирован AnimalProxy, может быть представлен как подклассовое животное, потому что он является реальным экземпляром правильного класса, вам нужно только получить ссылку this.

Ответ 3

Возможно, вы захотите попробовать прокси-тип (предположим, что NH 2.0 +):

((INHibernateProxy)proxy).HibernateLazyInitializer.PersistentClass

Но такой тип кастинга или "тип подглядывания" - это очень плохая практика в любом случае...

Ответ 4

Если мы работаем с той же проблемой, проблема заключается в том, что созданный прокси - это прокси-сервер, а не собака.

Решением, которое мы использовали, было перезагрузить объект:

Dog dog = this.CurrentSession.Load<Dog>(owner.OwnedAnimal.AnimalID);

Это возвращается к вашей сессии и перезагружает объект с правильным типом.

Надеюсь, что это поможет

Ответ 5

Если вы используете Fluent NHibernate, вы можете использовать переопределение автоматического отображения, чтобы отключить ленивую загрузку только для этого свойства:

public class DogOwnerMapOverride : IAutoMappingOverride<DogOwner>
{
    public void Override( AutoMapping<DogOwner> mapping )
    {
        mapping.References( x => x.OwnedAnimal ).Not.LazyLoad();
    }
}

Ответ 6

Вы можете попробовать поставить этот метод на свой основной объект:

public virtual T As<T>() where T : Entity {
      return this as T;
}