使用Linq从邻接模型列表中读取叶节点

时间:2014-11-10 07:01:33

标签: c# linq

我有一个邻接模型列表来存储层次结构,如下所示。表结构类似于Nothwind数据库中的employees表。示例如下。

  • 员工ID 1报告给员工ID 2

  • 员工ID 3报告给员工ID 2

  • 员工ID 4报告给员工ID 2

  • 员工ID 5报告给员工ID 3

  • 员工ID 6报告给员工ID 4

  • 员工ID 7报告给员工ID 5

  • 员工ID 8报告给员工ID 7。

我想知道叶节点员工的名单,即不是" Boss"对任何其他员工。在上面的示例中,它们是1,8和6.我尝试编写LINQ扩展以获取所有叶节点,如下所示。

        public static IEnumerable<TEntity> SelectDeep<TEntity, TProperty>(
                         this IEnumerable<TEntity> allItems,
                         Func<TEntity, TProperty> idProperty,
                         Func<TEntity, TProperty> parentIdProperty,
                         object rootItemId)
        {
            IEnumerable<TEntity> leve11Data = LevelDeep(allItems, default(TEntity), idProperty, parentIdProperty, rootItemId);
            IEnumerable<TProperty> leafOnly = leve11Data.Select(i => idProperty(i)).Except(leve11Data.Select(i => parentIdProperty(i)));
            IEnumerable<TEntity> childItemsOnly = allItems.Where(i => leafOnly.Contains(idProperty(i)));
            return childItemsOnly;
        }


      public static IEnumerable<TEntity> LevelDeep<TEntity, TProperty>(this IEnumerable<TEntity>allItems,
                    TEntity parentItem,
                    Func<TEntity, TProperty> idProperty,
                    Func<TEntity, TProperty> parentIdProperty,
                    object rootItemId)
        {
            IEnumerable<TEntity> childs;
            if (rootItemId != null)
            {
                childs = allItems.Where(i => parentIdProperty(i).Equals(rootItemId));
            }
            else
            {
                if (parentItem == null)
                {
                    childs = allItems.Where(i => parentIdProperty(i).Equals(default(TProperty)));
                }
                else
                {
                    childs = allItems.Where(i => parentIdProperty(i).Equals(idProperty(parentItem)));
                }
            }
            if (childs.Count() > 0)
            {
                foreach (TEntity item in childs)
                {
                    yield return item;
                    foreach (TEntity subItem in LevelDeep(allItems, item, idProperty, parentIdProperty, null))
                    {
                        yield return subItem;
                    }
                }
            }
        }

我用它来称呼它:

   (from listEntry in myList.SelectDeep(e => e.child_part_id, e => e.parent_part_id, 100).ToList()

但不幸的是我的扩展方法进入infinte循环,我无法弄清楚为什么.. 有人可以帮忙..

2 个答案:

答案 0 :(得分:0)

我认为您在递归调用中忘记了rootItemId参数。尝试将其更改为:

            foreach (TEntity item in childs)
            {
                yield return item;
                object itemId = idProperty(item);
                foreach (TEntity subItem in LevelDeep(allItems, item, idProperty, parentIdProperty, itemId))
                {
                    yield return subItem;
                }
            }

您实际上应该将所有ID的object类型更改为TProperty类型,以确保。

答案 1 :(得分:0)

最后我解决了!。

问题:实际上并不是无限循环。它发生在SelectDeep()调用LevelDeep()然后它使用变量level1Data操纵LevelDeep()的结果。由于变量level1Data并不真正评估表达式,每当我尝试访问level1Data时,LevelDeep()都会一次又一次地执行。从日志中我误解为无限递归。

解决方案:将另一个扩展方法FindLeafOnly()写为

public static IEnumerable<TEntity> FindLeafOnly<TEntity, TProperty>(
                           this IEnumerable<TEntity> leve11Data,
                           Func<TEntity, TProperty> idProperty,
                           Func<TEntity, TProperty> parentIdProperty)
            {   
                IEnumerable<TProperty> allChild = leve11Data.Select(i => idProperty(i));
                IEnumerable<TProperty> allParent = leve11Data.Select(i => parentIdProperty(i));
                IEnumerable<TProperty> leafOnly = allChild.Except(allParent);
                IEnumerable<TEntity> childItemsOnly = leve11Data.Where(i => leafOnly.Contains(idProperty(i)));
                return childItemsOnly;
            }

最后避免重复表达式评估的关键是在调用FindLeafOnly()之前调用SelectDeep()之后的ToList()。所以调用代码就像

   (from listItem in myAdjucencyModelList.SelectDeep(e => e.child_part_id, e => e.parent_part_id, 5412).ToList().FindLeafOnly(e=> e.child_part_id, e => e.parent_part_id).ToList()

ToList()确保在调用下一个Linq扩展之前评估第一个Linq扩展。