开发者

NHibernate: Prevent Refresh to trigger uninitialized Collections

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 c开发者_运维知识库ollection 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


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

        }
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜