문제

나는 아동 실체를로드하여 세션에서 만들어진 게으른 엔티티에 대한 대리가 있습니다. 부모 엔티티의 후속 페치는 NH 프록시 만 반환합니다. 유형을 확인하려면 실제 인스턴스가 필요합니다 (엔티티가 서브 클래스가 결합 됨). 나는 무언가를 놓치고 있어야하지만 이것을 할 방법을 찾을 수 없습니다. SESSION.REFRESH (프록시)는 도움이되지 않으며 내가 시도한 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 문서의.

다른 팁

제 생각에는이 문제를 해결하는 것이 오히려 디자인을 다시 생각해야합니다. 이 상황에서 다형성을 사용할 수 없다고 절대적으로 확신합니까? 나는이 문제를 몇 번이나 발견했고 항상 디자인을 변경하기로 결정했습니다. 유형에 의존하는 것이 최상의 솔루션이라고 확실하지 않으면 똑같이하는 것이 좋습니다.

문제

현실 세계와 적어도 유사한 예를 갖기 위해서는 다음과 같은 개체가 있다고 가정 해 봅시다.

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

이 문제에는 두 가지 해결책이 있으며 둘 다 다운 사이드가 있습니다. 선택한 스트림에 정보를 인쇄하기 위해 작동중인 초록/가상 메소드를 도입 할 수 있습니다. 그러나 이것은 UI 문제를 모델에 혼합하므로 허용되지 않습니다 (이 솔루션을 어떻게 개선하여 잠시 동안의 기대를 충족시킬 수 있는지 보여 드리겠습니다).

다음과 같은 형태로 많은 IFS를 만들 수도 있습니다.

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

보시다시피 - 전체 계층에 대한 프록시 클래스는 하나뿐입니다. 왜요? 콘크리트 유형에 의존하지 않는 방식으로 코드를 작성해야하기 때문에 제공된 추상화에만 코드를 작성해야합니다. 이 프록시는 엔티티로드를 제 시간에 연기 할 수 있습니다. 아마도 전혀 사용하지 않을까요? 아마도 1000 개 엔티티 중 2 개만 사용할 수 있습니까? 그렇다면 왜 모두로드합니까?

따라서 Nhibernate는 위의 위의 프록시를 사용하여 (훨씬 더 정교합니다) 엔티티 로딩을 연기합니다. 하위 유형 당 1 개의 프록시를 만들 수 있지만 게으른 하중의 전체 목적을 파괴 할 것입니다. nhibernate가 서브 클래스를 저장하는 방법에주의를 기울이면 유형 엔티티가 어떤 유형 엔티티인지 결정하려면로드해야합니다. 따라서 콘크리트 프록시를 갖는 것은 불가능합니다. 가장 추상적 인 OperationProxy 만 가질 수 있습니다.

IFS가있는 솔루션은 못 생겼습니다. 솔루션이었습니다. 이제 문제에 대한 프록시를 도입했을 때 더 이상 작동하지 않습니다. 그래서 우리에게 다형성 방법을 남겨 두어 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 Methods를 삭제했습니다)를 만듭니다.

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

여기서 어떻게됩니까? 운영에 대한 수락을 호출하고 방문자를 통과하면 적절한 방문 방법이 호출되는 경우 수락 구현이 호출됩니다 (컴파일러는 "this"의 유형을 결정할 수 있음). 따라서 가상 방법과 오버로드의 "전원"을 결합하여 적절한 방법을 가져옵니다. 보시다시피 - 이제 UI 참조, 모델은 모델 레이어에 포함될 수있는 인터페이스에만 의존합니다.

이제이 작업을 수행하기 위해 인터페이스의 구현을 수행합니다.

 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