Заставьте ленивую сущность загрузить реальный экземпляр

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

  •  05-07-2019
  •  | 
  •  

Вопрос

У меня есть прокси-сервер для ленивой сущности, которая была создана в сеансе путем загрузки дочерней сущности.Последующая выборка родительского объекта возвращает только прокси-сервер NH.Мне нужен фактический экземпляр, чтобы проверить тип (объект присоединился к подклассам).Должно быть, я чего-то не понимаю, но я не могу найти способ сделать это.Сессия.Обновление (прокси), похоже, не помогает, равно как и любой другой вариант HQL, который я пробовал.

Кто-нибудь может помочь?

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

Решение

Чтобы принудительно извлечь прокси-сервер из базы данных, вы можете использовать NHibernateUtil.Initialize(proxy) метод или доступ к методу / свойству прокси-сервера.

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

Чтобы проверить, инициализирован объект или нет, вы можете использовать NHibernateUtil.IsInitialized(proxy) способ.

Обновить:

Чтобы удалить объект из кэша сеанса, используйте Session.Evict(obj) способ.

session.Evict(myEntity);

Информация о Evict а другие методы управления кешем сеанса можно найти в глава 14.5 из документов NHibernate.

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

На мой взгляд, вместо того, чтобы решать эту проблему, вам следует переосмыслить свой дизайн.Вы абсолютно уверены, что не можете использовать полиморфизм в этой ситуации - либо напрямую назначить объект ответственным за операцию, которую вы пытаетесь выполнить, либо использовать шаблон visitor?Я сталкивался с этой проблемой несколько раз и всегда решал изменить дизайн - это приводило к более четкому коду.Я предлагаю вам сделать то же самое, если только вы не абсолютно уверены, что полагаться на type - лучшее решение.

В чем проблема

Чтобы иметь пример, имеющий хотя бы некоторое сходство с реальным миром, давайте предположим, что у вас есть следующие объекты:

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 operation).

Operation[] operations = { transfer, withdrawal };
foreach (var operation in operations)
{
    PrintOperation(operation);
}

Есть два решения этой проблемы, и у обоих есть недостатки.Вы можете ввести абстрактный / виртуальный метод в Действие для печати информации в выбранный поток.Но это привнесет проблемы пользовательского интерфейса в вашу модель, так что для вас это неприемлемо (я сейчас покажу вам, как вы можете улучшить это решение, чтобы оно соответствовало вашим ожиданиям).

Вы также можете создать множество "если" в форме:

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

Это решение некрасиво и подвержено ошибкам.Каждый раз, когда вы добавляете / изменяете / удаляете тип операции, вы должны просмотреть все места, где вы использовали эти взломы, и модифицировать их.И если вы пропустите одно место, вы, вероятно, сможете перехватить только эту среду выполнения - никаких строгих проверок во время компиляции на наличие некоторых ошибок (например, отсутствия одного подтипа).

Более того, это решение не сработает, как только вы введете какой-либо прокси.

Как работает прокси-сервер

Приведенный ниже код представляет собой ОЧЕНЬ простой прокси (в этой реализации он совпадает с шаблоном декоратора - но эти шаблоны в целом не совпадают.Потребовался бы некоторый дополнительный код, чтобы отличить эти два шаблона).

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

Как вы можете видеть - существует только один прокси-класс для всей иерархии.Почему?Потому что вы должны писать свой код таким образом, чтобы он не зависел от конкретного типа - только от предоставленной абстракции.Этот прокси-сервер может отложить загрузку объекта во времени - может быть, вы вообще не будете его использовать?Может быть, вы будете использовать только 2 объекта из 1000?Зачем тогда загружать их все?

Таким образом, NHibernate использует прокси, подобный описанному выше (хотя и гораздо более сложный), чтобы отложить загрузку объекта.Это могло бы создать по 1 прокси-серверу для каждого подтипа, но это разрушило бы всю цель отложенной загрузки.Если вы внимательно посмотрите на то, как NHibernate хранит подклассы, вы увидите, что для того, чтобы определить, к какому типу относится entity, вы должны загрузить его.Таким образом, невозможно иметь конкретные прокси-серверы - у вас может быть только самый абстрактный OperationProxy.

Несмотря на то, что решение с ifs это уродливо - это было решение.Теперь, когда вы ввели прокси-серверы в свою проблему - это больше не работает.Таким образом, это просто оставляет нас с полиморфным методом, что неприемлемо из-за смешивания ответственности пользовательского интерфейса с вашей моделью.Давайте это исправим.

Инверсия зависимостей и шаблон посетителей

Во-первых, давайте посмотрим, как будет выглядеть решение с виртуальными методами (просто добавлен код).:

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

все работает как по волшебству.

Чтобы удалить эту зависимость пользовательского интерфейса в модели, давайте создадим интерфейс:

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").Таким образом, вы объединяете "мощь" виртуальных методов и перегрузки, чтобы вызвать соответствующий метод.Как вы можете видеть - теперь ссылка на пользовательский интерфейс здесь, модель зависит только от интерфейса, который может быть включен в слой модели.

Итак, теперь, чтобы заставить это работать, рассмотрим реализацию интерфейса:

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

Я прекрасно понимаю, что это не идеальное решение.Вам все равно придется изменять интерфейс и посетителей по мере добавления новых типов.Но вы проверяете время компиляции и никогда ничего не пропустите.Одна вещь, которой было бы действительно трудно достичь с помощью этого метода, - это получить подключаемые подтипы, но я все равно не уверен, что это допустимый сценарий.Вам также придется изменить этот шаблон в соответствии с вашими потребностями в конкретном сценарии, но я оставляю это на ваше усмотрение.

Отключение отложенной загрузки приведет к принудительному возврату фактического экземпляра вместо прокси-сервера NHibernate.

например..

сопоставление.Not.LazyLoad();

или

<class name="OrderLine" table="OrderLine" lazy="false" >

Поскольку прокси является производным от класса сущностей, вы, вероятно, можете просто проверить entity.GetType (). BaseType, чтобы получить ваш определенный тип.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top