将List <T>排序为DropDownList的多级类别导航

Xee*_*vis 2 .net c# asp.net asp.net-mvc

让我先说明一下我的目标与我想要实现的目标

排序

在控制器中,我以非常随机的未排序方式将所有类别放入单个通用列表中

var categories = new List<Category>(this.categoryService.GetCategories())
Run Code Online (Sandbox Code Playgroud)

每个类别都有4个属性,这里有Id,ParentCategoryId,SortOrder,Text

SortOrder只能应用于层次结构中同一级别的兄弟姐妹,并且子级必须始终位于其父级之下.文本必须通过在每个深度级别前加上".."来改变.

我希望这可以在考虑到性能的情况下正确完成,不希望多次循环遍历大量列表.

感谢您的任何意见.

Iro*_*eek 7

这可能不是最高性能的代码,但应该适用于OP的问题.

由于OP没有明确定义有问题的数据结构(模型),我将假设它是这样的:

public class Category {
  public int Id { get; set; }
  public int ParentCategoryId { get; set; }
  public int SortOrder { get; set; }
  public string Text { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

要将category(List<Category>)列表排序为多级别类别,我们需要一个树状结构来保存数据.

首先,我将Category使用新属性扩展模型(类),例如Level(表示级别深度),Children(保存子/子Category)和DisplayText(根据级别显示类别文本):

public class CategoryNode : Category {
  public CategoryNode(Category category) {
    Id = category.Id;
    ParentCategoryId = category.ParentCategoryId;
    SortOrder = category.SortOrder;
    Text = category.Text;
  }

  public CategoryTree Children { get; set; }
  public int Level { get; set;}
  public string DisplayText { 
    get { 
      // OP wants two-dots prefix per level
      return string.Concat(new string('.', Level*2), Text); 
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

注意:取决于您的偏好,您可以Category直接更改类,而不是将其子类化CategoryNode.

接下来,我将定义一个类来包装被CategoryNode调用的集合CategoryTree,它是一个简单的包装器,List<CategoryNode>它暴露了一个IEnumerableinterace.我还要Flatten()CategoryTree其中添加一个方法,将树状结构展平为单个列表.这种方法可以方便地将数据绑定到单列表(非分层)控件,如DropDownListListBox.最后,我还添加了一个静态创建方法,Create()用于创建CatogoryTree基于给定列表的实例Category:

public class CategoryTree : IEnumerable<CategoryNode> {
  private List<CategoryNode> innerList = new List<CategoryNode>();

  public CategoryTree(IEnumerable<CategoryNode> nodes) {
    innerList = new List<CategoryNode>(nodes);  
  }

  public IEnumerator<CategoryNode> GetEnumerator()
  {
    return innerList.GetEnumerator();
  }

  System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  {
    return this.GetEnumerator();
  }

  public IEnumerable<CategoryNode> Flatten() {
    foreach(var category in innerList.OrderBy(o => o.SortOrder)) {
      yield return category;

      if (category.Children != null) {
        foreach(var child in category.Children.Flatten()) {
          yield return child;
        }
      }
    }
  }

  public static CategoryTree Create(
    IEnumerable<Category> categories, 
    Func<Category, bool> parentPredicate, 
    int level = 0) 
  {
    var nodes = categories
      .Where(parentPredicate)
      .OrderBy(o => o.SortOrder)
      .Select(item => new CategoryNode(item) { 
        Level = level,
        Children = Create(categories, o => o.ParentCategoryId == item.Id, level + 1)
      });

    return new CategoryTree(nodes);
  }
}    
Run Code Online (Sandbox Code Playgroud)

注意:再次,可以说,你可以直接使用List<CategoryNode>,提取方法,并在这里节省创建新类的麻烦.你的来电.

有了所有部分,我现在可以使用以下代码将Category(List<Category>)的列表转换为多级列表Category,并展平该列表以将其绑定到DropDownList:

...
var categories = new List<Category>(this.categoryService.GetCategories())

// assuming ParentCategoryId == 0 is the root category
var categoryTree = CategoryTree.Create(categories, o => o.ParentCategoryId == 0);

var model = new SomeViewModel();
model.Categories = new SelectList(categoryTree.Flatten(), , "Id", "DisplayText");

return View(model);
...    
@Html.DropDownListFor(m => m.SelectedCategory, Model.Categories)
...
Run Code Online (Sandbox Code Playgroud)

演示(使用Console.Out):https://ideone.com/ejOfrr