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

发布于 2024-10-25 12:48:47 字数 2913 浏览 1 评论 0原文

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

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

输出: 刷新后,集合不应该被初始化,但只是

为了澄清:

也许我可以在不删除cascade.all的情况下生活,但绝对不能没有cascade.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

我仍然不确定此操作的其他含义

I have an Entity with a Collection, which holds other entities having more References on Entities. When the Entity is loaded and later refreshed, the lazy loaded collection is triggered and a lot of stuff is pulled from database which i dont need. So how can i prevent refresh from triggering uninitialized collections?
I have written a small test which shows it.

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

and the following Test

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

Output:
After Refresh, collection should not be initialized but was

Just to clarify:

Maybe i can live without delete in cascade.all but definitly not without cascade.refresh.
I want refresh to cascade to the collection if it is initialized otherwise not, because it will be lazy loaded with fresh data when used.

I played a little with the NHibernate-code for the Refresh event. If I remove the fetch profile:

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;
    [...]
}

i get what i expect in the logs (removed aliases in SQL):

without initializing collection

Session.Get
DEBUG - SELECT Id  FROM "SimpleClass" WHERE Id=:p0;:p0 = 1
Refresh
DEBUG - SELECT Id FROM "SimpleClass" WHERE Id=:p0;:p0 = 1

with initializing collection (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

I'm still unsure about other implications of this

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

暗恋未遂 2024-11-01 12:48:57

如果您查看为刷新生成的 SQL,您将看到它使用左联接(而不是第二个查询,因此它不是延迟加载)加载子级,而第一个 Get 操作没有联接。

Refresh() 使用级联设置,并将加载级联设置为“全部”的集合,因此您的问题在这里:

HasMany(sc => sc.Childs)
            .Cascade.All()
            .LazyLoad();

如果您编写 Cascade.NoneCascade.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);

        }
    }
}

If you look at the generated SQL for the Refresh, you will see that it loads the Children with a left join (and not with a second query, so it is not lazily loaded), while the first Get operation does not have a join.

Refresh() uses the cascade settings and will load the collections where cascade is set to "all", so your issue is here:

HasMany(sc => sc.Childs)
            .Cascade.All()
            .LazyLoad();

If you write Cascade.None or Cascade.SaveUpdate, it should get the desired result. Of course, I don't know if you absolutely need Cascade.All. In that case I suggest replacing the Refresh() with the following:

m_session.Evict(simple);
simple = m_session.Get<Order>(1);

Edit:

You could examine the initialized-status and do the refresh depending on that. That is probably not the most beautiful solution, though.

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

        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文