Linq类别和子类别

时间:2016-03-05 21:55:23

标签: c# linq

问题是我不明白如何使用LINQ获取所有类别和子类别并将它们添加到列表中,以便我可以在视图中显示它们。

public class Category
{
    public int ID { get; set; }
    public string Title { get; set; }
    public int? ParentID { get; set; }
    public virtual Category Parent { get; set; }
}

目前我只有这种方法来获取所有类别。

public async Task<IEnumerable<Category>> GetAllCategories()
    {
        return await _context.Categories.ToListAsync();
    }

这就是我想要实现的目标:

output

3 个答案:

答案 0 :(得分:2)

所以,如果我从这个数据开始:

var categories = new List<Category>()
{
    new Category() { ID = 0, ParentID = null, Title = "First Link" },
    new Category() { ID = 1, ParentID = null, Title = "Second Link" },
    new Category() { ID = 2, ParentID = null, Title = "Third Link" },
    new Category() { ID = 3, ParentID = null, Title = "Fourth Link" },
    new Category() { ID = 4, ParentID = null, Title = "Fifth Link" },
    new Category() { ID = 5, ParentID = 0, Title = "First Child Link" },
    new Category() { ID = 6, ParentID = 0, Title = "Second Child Link" },
    new Category() { ID = 7, ParentID = 0, Title = "Third Child Link" },
    new Category() { ID = 8, ParentID = 6, Title = "First Grandchild Link" },
    new Category() { ID = 9, ParentID = 6, Title = "Second Grandchild Link" },
    new Category() { ID = 10, ParentID = 6, Title = "Third Grandchild Link" },
};

...然后我可以这样做:

var lookup = categories.ToLookup(x => x.ParentID);

这会创建一个查找,可用于查找任何父ID的所有子项。在您的情况下,您应该能够_context.Categories.ToLookup(x => x.ParentID);。您可能需要在.ToArray()之前点击.ToLookup

好消息是,这只会打击数据库一次。

现在递归遍历数据变得容易了。这有三种方法:

(1)

Func<ILookup<int?, Category>, int?, int, IEnumerable<string>> formatTree = null;
formatTree = (l, p, i) =>
    from c in l[p]
    from t in new[] { "".PadLeft(i * 4) + c.Title }.Concat(formatTree(l, c.ID, i + 1))
    select t;

(2)

public IEnumerable<string> FormatTree(ILookup<int?, Category> lookup, int? parent, int indent)
{
    return
        from c in lookup[parent]
        from t in new[] { "".PadLeft(indent * 4) + c.Title }.Concat(FormatTree(lookup, c.ID, indent + 1))
        select t;
}

(3)

public IEnumerable<string> FormatTree2(ILookup<int?, Category> lookup, int? parent, int indent)
{
    foreach (var category in lookup[parent])
    {
        yield return "".PadLeft(indent * 4) + category.Title;
        foreach (var descendant in FormatTree2(lookup, category.ID, indent + 1))
        {
            yield return descendant;
        }
    }
}

所有三个人用不同的语法以相同的方式做同样的事情。

我得到了这个输出:

First Link 
    First Child Link 
    Second Child Link 
        First Grandchild Link 
        Second Grandchild Link 
        Third Grandchild Link 
    Third Child Link 
Second Link 
Third Link 
Fourth Link 
Fifth Link 

目前尚不清楚您的确切输出是什么 - 我假设您不想创建PNG图像 - 但您应该能够使用它来获得所需的内容。

答案 1 :(得分:0)

分层树结构并不适合运行LINQ查询,因为(遗憾的是)需要扩展您想要包含的每个层次结构的查询。

此查询应该将所有顶级类别作为具有直接子项的组:

var query = from category in _context.Categories
            where category.ParentID == null
            from subCategory in _context.Categories
            where subCategory == category.ID
            group subCategory by category;

如果你只有两个级别的层次结构,这应该可以正常工作,但要真正获得任何类型顺序的树结构,你需要展平层次结构,这通常使用递归函数完成,每次调用它(单独)展平树的一个节点。

对于这种结构,标准SQL查询或LINQ查询并没有真正削减它。但是,实体框架(以及可能还有其他ORM解决方案)将为您完成所有工作。

您可以在模型中创建一些正确归因的导航属性,如下所示:

public virtual IEnumerable<Category> SubCategories { get; set; }
public virtual Category ParentCategory { get; set; }

但是如果您选择在模型中包含导航属性,则需要以艰难的方式执行此操作,每次调用递归函数时都会向数据层发出请求。

public void RenderSubCategoriesAsHtml(int? parentID = null)
{
    var children =
        from category in _context.Categories
        where category.ParentID == parentID
        select category;

    if (children.Any())
    {
        Response.WriteLine("<ul>");
        foreach (var child in children)
        {
            Response.WriteLine("<li><a href=\"#addProperUrl\">");
            Response.WriteLine(Server.EncodeHtml(child.Title));
            Response.WriteLine("</a>");
            RenderSubCategoriesAsHtml(child.ID);
            Response.WriteLine("</li>");
        }
        Response.WriteLine("</ul>");
    }
}

只需将对Response.WriteLine的调用更改为更适合您应用程序架构的内容。

答案 2 :(得分:0)

EF有办法做到这一点。但您必须在CategorySubcategory

之间建立外键关系
var all categoriesWithSubCategories =  _context.Categories.Include("Subcategory").ToList();

现在,当您从categoriesWithSubCategories列表中选择一个类别时,您将可以访问属于所选类别的所有子类别。

添加更多细节以进一步解释;

正如我之前提到的,你必须在同一个表上建立FK关系才能做到这一点。

这意味着您的ParentId字段与您的Id字段有外键关系,这是您的PK字段。

现在稍微更改您的实体类以表示上述关系。

public partial class Category
{
    public Category()
    {
        this.SubCategory = new HashSet<Category>();
    }

    public int Id { get; set; }
    public string Title { get; set; }
    public Nullable<int> ParentId { get; set; }

    public virtual ICollection<Category> SubCategory { get; set; }
    public virtual Category ParentCategory { get; set; }
}

一旦完成,您只需要一行Linq(上图)就可以为您的类别和子类别构建完美的树层次结构。