简体   繁体   中英

preloading a tree / hierarchy from database using NHibernate / C#

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM