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

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

  •  03-07-2019
  •  | 
  •  

Вопрос

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

Модель предметной области

Допустим, у меня есть две параллельные иерархии классов:Животное, Собака, Кошка и AnimalOwner, DogOwner, CatOwner, где Собака и Кошка оба наследуют от 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, все свойства являются постоянными, и все, что может быть загружено с задержкой, загружается с задержкой.

Бизнес-логика приложения позволяет вам установить только Dog в DogOwner и Cat в CatOwner.

Проблема

У меня есть такой код, как этот:

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

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

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

Ограничения

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

Спасибо,
Нир

Это было полезно?

Решение

Проще всего отключить отложенную загрузку для класса animal.Ты все равно говоришь, что это в основном в памяти.

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

Другие советы

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

public Animal Self { get { return this; } }

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

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

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

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

((INHibernateProxy)proxy).HibernateLazyInitializer.PersistentClass

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

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

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

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();
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top