Sam*_*Sam 7 nhibernate lazy-evaluation
我有一个懒惰实体的代理,它通过加载子实体在会话中创建.父实体上的后续提取仅返回NH代理.我需要实际的实例来检查类型(实体已经加入了子类).我必须遗漏一些东西,但我找不到办法做到这一点.Session.Refresh(代理)似乎没有帮助,也没有尝试过的任何HQL风格.
有人可以帮忙吗?
mac*_*kow 21
在我看来,而不是解决这个问题,你应该重新考虑你的设计.您是否绝对肯定,在这种情况下您不能使用多态 - 要么直接让实体负责您尝试执行的操作,要么使用访问者模式.我几次遇到这个问题并且总是决定改变设计 - 它产生了更清晰的代码.我建议你这样做,除非你绝对确定依靠类型是最好的解决方案.
为了得到与现实世界至少有一些相似性的例子,让我们假设您有以下实体:
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; }
}
Run Code Online (Sandbox Code Playgroud)
它自然只是更大型号的一小部分.现在你遇到了一个问题:对于每种具体类型的操作,有一种不同的方式来显示它:
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);
}
Run Code Online (Sandbox Code Playgroud)
简单的重载方法将在简单的情况下工作:
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);
Run Code Online (Sandbox Code Playgroud)
遗憾的是,重载方法在编译时受到约束,因此只要引入数组/列表/任何操作,就会调用泛型(操作操作)重载.
Operation[] operations = { transfer, withdrawal };
foreach (var operation in operations)
{
PrintOperation(operation);
}
Run Code Online (Sandbox Code Playgroud)
这个问题有两种解决方案,两者都有缺点.您可以在"操作"中引入抽象/虚拟方法,以将信息打印到选定的流.但这会将UI问题混合到您的模型中,因此您无法接受(我将告诉您如何在一瞬间改进此解决方案以满足您的期望).
您还可以通过以下形式创建大量ifs:
if(operation is (ConcreteType))
PrintOperation((ConcreteType)operation);
Run Code Online (Sandbox Code Playgroud)
这种解决方案很丑陋且容易出错.每次添加/更改/删除操作类型时,您都必须遍历您使用这些黑客的每个地方并进行修改.如果你错过了一个地方,你可能只能捕获那个运行时 - 没有严格的编译时检查一些错误(比如缺少一个子类型).
此外,一旦引入任何类型的代理,此解决方案将失败.
下面的代码是非常简单的代理(在这个实现中它与装饰器模式相同 - 但是这些模式通常不一样.它需要一些额外的代码来区分这两种模式).
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; }
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见 - 整个层次结构只有一个代理类.为什么?因为您应该以不依赖于具体类型的方式编写代码 - 仅限于提供的抽象.这个代理可以及时推迟实体加载 - 也许你根本不会使用它?也许你只会使用1000个实体中的2个?为什么要加载它们呢?
所以NHibernate使用上面的代理(虽然更复杂)推迟实体加载.它可以为每个子类型创建1个代理,但它会破坏延迟加载的整个目的.如果你仔细看看NHibernate如何存储你将看到的子类,那么为了确定实体是什么类型,你必须加载它.所以不可能有具体的代理 - 你只能拥有最抽象的OperationProxy.
尽管这很丑陋,但这是一个解决方案.现在,当您为问题引入代理时 - 它已不再有效.因此,我们只使用多态方法,因为将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();
}
}
Run Code Online (Sandbox Code Playgroud)
现在,当你打电话:
Operation[] operations = { transfer, withdrawal, proxy };
foreach (var operation in operations)
{
operation.PrintInformation();
}
Run Code Online (Sandbox Code Playgroud)
一切都是魅力.
为了在模型中删除此UI依赖项,让我们创建一个接口:
public interface IOperationVisitor
{
void Visit(AtmWithdrawal operation);
void Visit(OutgoingTransfer operation);
}
Run Code Online (Sandbox Code Playgroud)
让我们修改模型以依赖于这个接口:
现在创建一个实现 - 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);
}
}
Run Code Online (Sandbox Code Playgroud)
这里发生了什么?当您在操作上调用Accept并传递访问者时,将调用accept的实现,其中将调用Visit方法的适当重载(编译器可以确定"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
}
Run Code Online (Sandbox Code Playgroud)
和代码:
Operation[] operations = { transfer, withdrawal, proxy };
foreach (var operation in operations)
{
operation.Accept(visitor);
}
Run Code Online (Sandbox Code Playgroud)
我很清楚这不是一个完美的解决方案.在添加新类型时,您仍然需要修改界面和访问者.但是你得到编译时检查,永远不会错过任何东西.使用这种方法真正难以实现的一件事是获得可插拔的子类型 - 但我不相信这是一个有效的场景.您还必须修改此模式以满足您在具体方案中的需求,但我会留给您.
Eri*_*ebo 12
要强制从数据库中提取代理,您可以使用该NHibernateUtil.Initialize(proxy)方法,或访问代理的方法/属性.
var foo = session.Get<Foo>(id);
NHibernateUtil.Initialize(foo.Bar);
Run Code Online (Sandbox Code Playgroud)
要检查对象是否已初始化,可以使用该NHibernateUtil.IsInitialized(proxy)方法.
更新:
要从会话高速缓存中删除对象,请使用该Session.Evict(obj)方法.
session.Evict(myEntity);
Run Code Online (Sandbox Code Playgroud)
有关Evict管理会话缓存的信息和其他方法可以在NHibernate文档的第14.5章中找到.
| 归档时间: |
|
| 查看次数: |
13854 次 |
| 最近记录: |