GroupJoin产生非常令人惊讶的结果

时间:2014-08-15 12:24:42

标签: c# entity-framework

我有一个如下所示的数据库表:

MenuItemID       |    int   [Primary Key]   1 -----+
MenuItemName     |    nvarchar(100)                |
ParentID         |    int                   * -----+

正如您在上面的示意图中所看到的,ParentID列引用了MenuItemID,因此我们可以获得分层输出。

上表中的样本数据如下所示:

enter image description here

从上面的示例数据中,我希望输出如下:

Electronics
    Mobile
    Desktop
    Laptop
        Lenovo
        Dell
Sports
    Cricket
    Football
    Hockey
Stationary
    Books
    Pens
    Pencils
    Erasers

我尝试了什么:

我已经尝试了下面提到的代码,我尝试使用GroupJoin扩展方法来实现所需的输出:

class Program
{
    static void Main(string[] args)
    {
        OnlineShoppingEntities db = new OnlineShoppingEntities();

        var x = db.MenuItems.GroupJoin(db.MenuItems,
                                       m => m.MenuItemID,
                                       m => m.ParentID,
                                       (parentMenuItems, childMenuItems) => new
                                       {
                                           ParentMenuItems = parentMenuItems,
                                           ChildMenuItems = childMenuItems
                                       });

        foreach (var v in x)
        {
            Console.WriteLine(v.ParentMenuItems.MenuItemName);
            foreach (var m in v.ChildMenuItems)
            {
                Console.WriteLine("\t" + m.MenuItemName);
            }
        }
    }
}

我得到的输出:

enter image description here

我对输出非常惊讶,因为我预期上面提到的所需输出。

2 个答案:

答案 0 :(得分:2)

您的解决方案可以在SO:Hierarchical data in Linq - options and performance找到。

我不会抽象代码因为有那么多答案可以解决你的问题,我特别喜欢AsHierarchy() linq扩展方法,如Kyle对该问题的回答所示。

var hierachy = dc.Employees.ToList().AsHierarchy(e => e.EmployeeID, e => e.ReportsTo);

code for the extension by Stefan Cruysberghs是:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ScipBe.Common.LinqExtensions
{
  // Stefan Cruysberghs, http://www.scip.be, March 2008

  /// <summary>
  /// Hierarchy node class which contains a nested collection of hierarchy nodes
  /// </summary>
  /// <typeparam name="T">Entity</typeparam>
  public class HierarchyNode<T> where T : class
  {
    public T Entity { get; set; }
    public IEnumerable<HierarchyNode<T>> ChildNodes { get; set; }
    public int Depth { get; set; }
  }

  public static class LinqExtensionMethods
  {
    private static System.Collections.Generic.IEnumerable<HierarchyNode<TEntity>> CreateHierarchy<TEntity, TProperty>
      (IEnumerable<TEntity> allItems, TEntity parentItem, 
      Func<TEntity, TProperty> idProperty, Func<TEntity, TProperty> parentIdProperty, int depth) where TEntity : class
    { 
      IEnumerable<TEntity> childs;

      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)
      {
        depth++;

        foreach (var item in childs)
          yield return new HierarchyNode<TEntity>() { Entity = item, ChildNodes = CreateHierarchy<TEntity, TProperty>
            (allItems, item, idProperty, parentIdProperty, depth), Depth = depth };
      }
    }

    /// <summary>
    /// LINQ IEnumerable AsHierachy() extension method
    /// </summary>
    /// <typeparam name="TEntity">Entity class</typeparam>
    /// <typeparam name="TProperty">Property of entity class</typeparam>
    /// <param name="allItems">Flat collection of entities</param>
    /// <param name="idProperty">Reference to Id/Key of entity</param>
    /// <param name="parentIdProperty">Reference to parent Id/Key</param>
    /// <returns>Hierarchical structure of entities</returns>
    public static System.Collections.Generic.IEnumerable<HierarchyNode<TEntity>> AsHierarchy<TEntity, TProperty>
      (this IEnumerable<TEntity> allItems, Func<TEntity, TProperty> idProperty, Func<TEntity, TProperty> parentIdProperty)
      where TEntity : class
    {
      return CreateHierarchy(allItems, default(TEntity), idProperty, parentIdProperty, 0);
    }
  }
}

答案 1 :(得分:0)

当然,如果您的数据只有3个级别,那么为简单起见,您可以使用其他GroupJoin()执行此操作:

var hierarchical = menuItems.Where(m => m.ParentID == null)
                                .GroupJoin(menuItems,
                                            m => m.MenuItemID, 
                                            m => m.ParentID,
                                            (parentMenuItems, childMenuItems) => new
                                            {
                                                ParentMenuItems = parentMenuItems,
                                                ChildMenuItems = childMenuItems.GroupJoin(menuItems,
                                                m => m.MenuItemID, 
                                                m => m.ParentID,
                                                (subParentMenuItems, subChildMenuItems) => new
                                                {
                                                    ParentMenuItems = subParentMenuItems,
                                                    ChildMenuItems = subChildMenuItems
                                                })
                                            });

    foreach(var menu in hierarchical)
    {
        Console.WriteLine(menu.ParentMenuItems.MenuItemName);
        foreach(var submenu in menu.ChildMenuItems)
        {
            Console.WriteLine("\t" + submenu.ParentMenuItems.MenuItemName);
            foreach(var subitem in submenu.ChildMenuItems)
            {
                Console.WriteLine("\t\t" + subitem.MenuItemName);
            }
        }
    }