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

本文关键字:列表 读取 叶节点 模型 Linq 使用 | 更新日期: 2023-09-27 18:29:53

我有一个邻接模型列表,用于存储如下层次结构。该表结构类似于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报告。

我想知道叶节点员工的列表,即对任何其他员工来说都不是"老板"的员工。在上述示例中,它们分别为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()

但不幸的是,我的扩展方法进入了infint循环,我不知道为什么。。有人能帮忙吗。。

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

我认为您在递归调用中忘记了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类型,只是为了确保。

我终于解决了!。

问题:实际上并不是无限循环。SelectDeep()调用LevelDeep(。由于变量level1Data并没有真正计算表达式,所以每当我试图访问level1DData时,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扩展在调用下一个之前进行了评估。