在NHibernate中获取树数据的最佳方法

sql*_*bie 5 nhibernate

我想从一个具有以下定义的表中获取下面的Hierarchical/Tree数据.

Tree Table:
"""""""""""
Id   |ParentId
"""""""""""
Work1|null
Work2|Work1
Work3|Work2
...
Run Code Online (Sandbox Code Playgroud)

必需的查询结果数据(不需要选项卡) - 如果我选择'Work1',我应该完成其根目录下的ID.如果我选择'Work2',那么我也应该在它的根之上和之下完成ID.

> Work1 
----------
>   Work2
----------
>     Work3
---------
Run Code Online (Sandbox Code Playgroud)

NHibernate以优化的方式在上述场景中获取数据的最佳方式是什么.

Flo*_*Lim 9

要找出"最佳方式"是什么,需要有关实际情况的更多信息.您在寻找什么样的"优化"?最少量的数据(只有你真正需要的行)或最少数量的SQL查询(最好是一次往返数据库)或其他任何数据?

场景1:一次加载并在内存中保留较长时间的菜单或树结构(不是每隔几秒更新一次的列表).表中的行数很少(小是相对的,但我会说200以下的任何行).

在这种情况下,我只需要使用以下一个查询获取整个表:

var items = session.Query<Work>()
    .Fetch(c => c.ParentWork)
    .Fetch(c => c.ChildWorks).ToList();

var item = session.Get<Work>(id);
Run Code Online (Sandbox Code Playgroud)

这将导致单个SQL查询,它只是从表中加载所有行.item将包含完整的树(父母,祖父母,孩子等).

场景2:大量行,只需要一小部分行.预计层次结构中只有少数级别.

在这种情况下,只需加载项目,并通过延迟加载让NHibernate到其余部分,或强制它通过编写递归方法来遍历父项和子项来加载所有内容.这将导致N + 1选择,这可能会或可能不会比情景1慢(取决于您的数据).

这是一个快速的黑客证明:

var item = session.Get<Work>(id);

Work parent = item.ParentWork;
Work root = item;
// find the root item
while (parent != null)
{
    root = parent;
    parent = parent.ParentWork;
}
// scan the whole tree
this.ScanChildren(root);
// -----
private void ScanChildren(Work item)
{
    if (item == null)
    {
        return;
    }

    foreach (Work child in item.ChildWorks)
    {
        string name = child.Name;
        this.ScanChildren(child);
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:

场景3:大量数据.查询次数最少,数据量最小.

在这种情况下,我认为不是树结构,而是我们一个接一个地加载数据层.

var work = repo.Session.Get<Work>(id);

// get root of that Work
Work parent = work.ParentWork;
Work root = work;
while (parent != null)
{
    root = parent;
    parent = parent.ParentWork;
}

// Get all the Works for each level
IList<Work> worksAll = new List<Work>() { root };
IList<Work> worksPerLevel = new List<Work>() { root };

// get each level until we don't have any more Works in the next level
int count = worksPerLevel.Count;
while (count > 0)
{
    worksPerLevel = this.GetChildren(session, worksPerLevel);
    // add the Works to our list of all Works
    worksPerLevel.ForEach(c => worksAll.Add(c));
    count = worksPerLevel.Count;
}

// here you can get the names of the Works or whatever
foreach (Work c in worksAll)
{
    string s = c.Name;
}

// this methods gets the Works in the next level and returns them
private IList<Work> GetChildren(ISession session, IList<Work> worksPerLevel)
{
    IList<Work> result = new List<Work>();
    // get the IDs for the works in this level
    IList<int> ids = worksPerLevel.Select(c => c.Id).ToList();

    // use a WHERE IN clause do get the Works 
    // with the ParentId of Works in the current level
    result = session.QueryOver<Work>()
        .Where(
            NHibernate.Criterion.Restrictions.InG<int>(
                NHibernate.Criterion.Projections.Property<Work>(
                    c => c.ParentWork.Id),
                ids)
        )
        .Fetch(c => c.ChildWorks).Eager // this will prevent the N+1 problem
        .List();

    return result;
}
Run Code Online (Sandbox Code Playgroud)

这个解决方案不会导致N + 1问题,因为我们对子节点使用了一个急切的负载,因此NHibernate将知道子列表的状态而不是再次命中DB.您将只获得x + y选择,其中x是查找根的选择数,Worky是级别数(树的最大深度).