NHibernate parent, child collection with futures
I have this setup: Parent, with a collection of Child.
class Parent {
IList<Child> Childs { get; set; }
}
HQL:
("From Parent").Future();
("From Child").Future();
foreach(Parent p in result) 开发者_Python百科{
foreach(Child c in p.Childs) {
}
}
This gives the classic N+1 problem. Two SQL statements are sent to server in 1 roundtrip, so all the data exists in first level cache, so why does NH still exists SQL for every child.
Version 3.1.0.400
When you execute the future query, you pull all Parent and Child objects into the 1st-level cache. The Parent objects contain a lazy collection, which needs to be populated. To populate the collection, NHibernate has to query the database. (We'll get to why in just a second.) The query returns Child objects and those child objects are already in the L1 cache. So those objects are used to populate the collection.
Now why does NHibernate have to query the database to populate the Childs collection? You could have a "where" clause on the collection that filters out Child objects with IsDeleted==true. You could have code in an EventListener that filters out certain Child objects. Basically there is a lot that can happen and NHibernate can't make any assumptions about the relationship between Parent and Child objects.
You can give it enough information by specifying a fetching strategy in the HQL or in your mapping. In HQL, you could write:
var parents = session.CreateQuery("from Parent p join fetch p.Childs").Future<Parent>();
The Child object query using the future would be completely optional as you're fetching the children with the parents. Because of the join fetch, you will get duplicate Parent objects, though they'll be the same object. (You're doing an inner join in the database and returning one copy of the parent row for each child row.) You can get rid of these by iterating over parents.Distinct().
If you always want to fetch Child objects with the corresponding Parent, you can also use fetch="join" in your Parent mapping.
<bag name="Children" cascade="all-delete-orphan" fetch="join">
<key column="ParentId"/>
<one-to-many class="Child"/>
</bag>
If neither of these options works for your scenario, you can specify batch-size on the collection mapping. You will still execute a database query when you hit parent.Childs, but NHibernate will eagerly initialize any other collection proxies.
<bag name="Children" cascade="all-delete-orphan" batch-size="10">
<key column="ParentId"/>
<one-to-many class="Child"/>
</bag>
精彩评论