Skip to main content
 首页 » 编程设计

c#之NHibernate:防止刷新触发未初始化的集合

2024年01月12日31leader

我有一个带有集合的实体,该实体包含对实体有更多引用的其他实体。当实体被加载并稍后刷新时,延迟加载的集合被触发,并且从数据库中提取了很多我不需要的东西。那么如何防止刷新触发未初始化的集合呢? 我写了一个小测试来展示它。

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