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