我有一个带有集合的实体,该实体包含对实体有更多引用的其他实体。当实体被加载并稍后刷新时,延迟加载的集合被触发,并且从数据库中提取了很多我不需要的东西。那么如何防止刷新触发未初始化的集合呢? 我写了一个小测试来展示它。
class SimpleClass
{
public virtual int Id { get; set; }
public virtual IList<ChildClass> Childs { get; set; }
}
class ChildClass
{
public virtual int Id { get; set; }
}
class SimpleClassMap : ClassMap<SimpleClass>
{
public SimpleClassMap()
{
Id(sc => sc.Id).GeneratedBy.Assigned();
HasMany(sc => sc.Childs)
.Cascade.All()
.LazyLoad();
}
}
class ChildClassMap : ClassMap<ChildClass>
{
public ChildClassMap()
{
Id(cc => cc.Id).GeneratedBy.Assigned();
}
}
和下面的测试
[Fact]
public void Test()
{
m_session.Save(new SimpleClass
{
Id = 1,
Childs = new List<ChildClass>
{
new ChildClass { Id = 1 },
new ChildClass { Id = 2 },
new ChildClass { Id = 3 },
}
});
m_session.Flush();
m_session.Clear();
log.Debug("Start Test");
var simple = m_session.Get<SimpleClass>(1);
var persistentcollection = simple.Childs as IPersistentCollection;
Assert.False(persistentcollection.WasInitialized, "Before Refresh, collection should not be initialized but was");
log.Debug("Refresh");
m_session.Refresh(simple);
Assert.False(persistentcollection.WasInitialized, "After Refresh, collection should not be initialized but was");
}
输出: 刷新后,集合不应该被初始化,而是被
澄清一下:
也许我可以在没有 delete in cascade.all 的情况下生活,但绝对不能没有 cascade.refresh。 如果它已初始化,我希望刷新级联到集合,否则不会,因为它会在使用时延迟加载新数据。
我对 Refresh 事件的 NHibernate 代码进行了一些尝试。如果我删除获取配置文件:
DefaultRefreshEventListener {
public virtual void OnRefresh(RefreshEvent @event, IDictionary refreshedAlready)
{
[...]
string previousFetchProfile = source.FetchProfile;
//source.FetchProfile = "refresh";
object result = persister.Load(id, obj, @event.LockMode, source);
//source.FetchProfile = previousFetchProfile;
[...]
}
我在日志中得到了我期望的结果(删除了 SQL 中的别名):
没有初始化集合
Session.Get
DEBUG - SELECT Id FROM "SimpleClass" WHERE Id=:p0;:p0 = 1
Refresh
DEBUG - SELECT Id FROM "SimpleClass" WHERE Id=:p0;:p0 = 1
使用初始化集合(simple.Childs.Count();)
Session.Get
DEBUG - SELECT Id FROM "SimpleClass" WHERE Id=:p0;:p0 = 1
simple.Childs.Count();
DEBUG - SELECT SimpleClass_id, Id, Id FROM "ChildClass" WHERE SimpleClass_id=:p0;:p0 = 1
Refresh
DEBUG - SELECT Id FROM "ChildClass" WHERE Id=:p0;:p0 = 1
DEBUG - SELECT Id FROM "ChildClass" WHERE Id=:p0;:p0 = 2
DEBUG - SELECT Id FROM "ChildClass" WHERE Id=:p0;:p0 = 3
DEBUG - SELECT Id FROM "SimpleClass" WHERE Id=:p0;:p0 = 1
我仍然不确定这个的其他含义
请您参考如下方法:
如果查看为 Refresh 生成的 SQL,您会发现它使用左连接加载子项(而不是使用第二个查询,因此它不是延迟加载),而第一个 Get 操作没有加入。
Refresh() 使用级联设置并将加载级联设置为“全部”的集合,因此您的问题在这里:
HasMany(sc => sc.Childs)
.Cascade.All()
.LazyLoad();
如果您编写 Cascade.None 或 Cascade.SaveUpdate,它应该会得到想要的结果。当然,我不知道你是否绝对需要Cascade.All。在这种情况下,我建议用以下内容替换 Refresh():
m_session.Evict(simple);
simple = m_session.Get<Order>(1);
编辑:
您可以检查初始化状态并根据它进行刷新。不过,这可能不是最漂亮的解决方案。
class SimpleClassDAL
{
[...]
public void Refresh(ISession session, SimpleClass simple)
{
var persistentcollection = simple.Childs as IPersistentCollection;
if (persistentcollection.WasInitialized)
{
// get everything again
session.Refresh(simple);
}
else
{
// only refresh simple
session.Evict(simple);
simple = session.Get<SimpleClass>(simple.Id);
}
}
}
