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);
}
}
}
精彩评论