I need to populate some tree hierarchies and traverse them in order to build up a category menu. Each category can have more than 1 parent. The problem is how to do this efficiently, and try to avoid the Select N+1
problem.
Currently, it is implemented by using two tables / entities:
Category
--------
ID
Title
CategoryLink
---------
ID
CategoryID
ParentID
Ideally, I would use the normal object traversal to go through the nodes, ie by going through Category.ChildCategories
, etc. Is this possible to be done in one SQL statement? And also, can this be done in NHibernate?
Specify a batch-size
on the Category.ChildCategories
mapping. That will cause NHibernate to fetch children in batches of the specified size, not one at a time (which will alleviate the N+1 problem).
If you are using .hbm
files, you can specify the batch-size
like this:
<bag name="ChildCategories" batch-size="30">
or using fluent mapping
HasMany(x => x.ChildCategories).KeyColumn("ParentId").BatchSize(30);
See the NHibernate documentation for more info.
EDIT
Ok, I believe I understand your requirements. With the following configuration
HasManyToMany<Item>(x => x.ChildCategories)
.Table("CategoryLink")
.ParentKeyColumn("ParentId")
.ChildKeyColumn("CategoryID")
.BatchSize(100)
.Not.LazyLoad()
.Fetch.Join();
you should be able to get the entire hierarchy in one call using the following line.
var result = session.CreateCriteria(typeof(Category)).List();
For some reason, retrieving a single category like this
var categoryId = 1;
var result = session.Get<Category>(categoryId);
results in one call per level in the hierarchy. I believe this should still significantly reduce the number of calls to the database, but I was not able to get the example above to work with a single call to the database.
This would retrieve all the categories with their children:
var result = session.Query<Category>()
.FetchMany(x => x.ChildCategories)
.ToList();
The problem is determining what the root categories are. You could either use a flag, or map the inverse collection ( ParentCategories
) and do this:
var root = session.Query<Category>()
.FetchMany(x => x.ChildCategories)
.FetchMany(x => x.ParentCategories)
.ToList()
.Where(x => !x.ParentCategories.Any());
All sorting should be done client-side (ie after ToList
)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.