Nhibernate 强制惰性实体加载真实实例

Nhibernate 强制惰性实体加载真实实例,nhibernate,lazy-evaluation,Nhibernate,Lazy Evaluation,我有一个惰性实体的代理,它是通过加载子实体在会话中创建的。父实体上的后续获取仅返回NH代理。我需要实际的实例来检查类型(实体已加入子类)。我一定错过了什么,但我找不到办法。刷新(代理)似乎没有帮助,我尝试过的任何HQL风格也没有帮助 有人能帮忙吗?由于代理是从实体类派生的,您可能只需检查entity.GetType().BaseType即可获得定义的类型。要强制从数据库中提取代理,您可以使用NHibernateUtil.Initialize(proxy)方法,或访问代理的方法/属性 var fo

我有一个惰性实体的代理,它是通过加载子实体在会话中创建的。父实体上的后续获取仅返回NH代理。我需要实际的实例来检查类型(实体已加入子类)。我一定错过了什么,但我找不到办法。刷新(代理)似乎没有帮助,我尝试过的任何HQL风格也没有帮助


有人能帮忙吗?

由于代理是从实体类派生的,您可能只需检查entity.GetType().BaseType即可获得定义的类型。

要强制从数据库中提取代理,您可以使用
NHibernateUtil.Initialize(proxy)
方法,或访问代理的方法/属性

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

可以在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);
这种解决方案难看且容易出错。每次添加/更改/删除操作类型时,您都必须检查使用这些黑客的每个地方并对其进行修改。如果您错过了一个位置,您可能只能捕获该运行时—对于某些错误(例如缺少一个子类型),没有严格的编译时检查

此外,只要引入任何类型的代理,此解决方案就会失败

代理如何工作 下面的代码是非常简单的代理(在这个实现中,它与decorator模式相同-但是这些模式通常不相同。需要一些额外的代码来区分这两种模式)

如您所见,整个层次结构只有一个代理类。为什么?因为您应该以一种不依赖于具体类型的方式编写代码——只依赖于提供的抽象。这个代理可以延迟实体加载的时间-也许你根本不会使用它?也许你会使用1000个实体中的2个?那为什么要把它们都装进去呢

因此,NHibernate使用上面类似的代理(尽管要复杂得多)来延迟实体加载。它可以为每个子类型创建一个代理,但它会破坏延迟加载的整个目的。如果您仔细观察NHibernate如何存储子类,您将看到,为了确定实体是什么类型,您必须加载它。因此,不可能有具体的代理—您只能有最抽象的OperationProxy

虽然ifs的解决方案很难看,但这是一个解决方案。现在,当您为您的问题引入代理时,它不再工作了。因此,这就给我们留下了多态方法,这是不可接受的,因为混合了对模型的UI责任。让我们来解决这个问题

依赖倒置和访问者模式 首先,让我们看看使用虚拟方法的解决方案是什么样子的(只是添加了代码):

现在,当你打电话时:

Operation[] operations = { transfer, withdrawal, proxy };
foreach (var operation in operations)
{
    operation.PrintInformation();
}
一切都是一种魅力

为了删除模型中的此UI依赖项,让我们创建一个接口:

public interface IOperationVisitor
{
    void Visit(AtmWithdrawal operation);
    void Visit(OutgoingTransfer operation);
}
 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
}
让我们修改模型以依赖于此接口:

public interface IOperationVisitor
{
    void Visit(AtmWithdrawal operation);
    void Visit(OutgoingTransfer operation);
}
 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
}
现在创建一个实现——ConsoleOutputOperationVisitor(我已经删除了PrintInformation方法):

这里发生了什么?当您在操作中调用Accept并传递访问者时,将调用Accept的实现,其中将调用适当的访问方法重载(编译器可以确定“this”的类型)。所以,您可以结合虚拟方法和重载的“威力”来调用适当的方法。正如您可以看到的,现在这里的UI引用,模型只依赖于一个接口,该接口可以包含在模型层中

现在,为了让它工作,接口的一个实现:

public interface IOperationVisitor
{
    void Visit(AtmWithdrawal operation);
    void Visit(OutgoingTransfer operation);
}
 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代理

例如

mapping.Not.LazyLoad()



您能展示一下您的域对象的外观以及您到目前为止尝试过的HQL查询吗?我不确定我是否能以这种格式提供大量的信息。您是否暗示应该可以编写忽略缓存中匹配代理的HQL查询?不,他不能。“实体已加入子类”。这意味着,代理将始终为b
<class name="OrderLine" table="OrderLine" lazy="false" >