LINQ query to get recursive category structure and products of each category

时间:2018-02-03 11:15:22

标签: c# linq recursion entity-framework-core

How can I re-write my query to include all the products of each category in a recursive product category tree? This query gets the categories and their children, so I'm just missing the products:

var categories = _context.ProductCategories
                .Include(e => e.Children)
                .Where(e => e.ParentId == null)
                .ToList();

Entity models:

public class ProductCategory
{
    public int Id { get; set; }
    public int SortOrder { get; set; }
    public string Title { get; set; }
    [ForeignKey(nameof(ParentCategory))]
    public int? ParentId { get; set; }
    // Nav.props:
    public ProductCategory ParentCategory { get; set; }
    public ICollection<ProductCategory> Children { get; set; }
    public List<ProductInCategory> ProductInCategory { get; set; }
}

public class ProductInCategory
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public int SortOrder { get; set; }
    public int ProductCategoryId { get; set; }
    // Nav.props:
    public Product Product { get; set; }
    public ProductCategory ProductCategory { get; set; }
}

public class Product
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Info { get; set; }
    public decimal Price { get; set; }
    // Nav.props:
    public List<ProductInCategory> InCategories { get; set; }
}

Viewmodel:

public class ViewModelProductCategory
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Title { get; set; }
    public int SortOrder { get; set; }

    public string ProductCountInfo
    {
        get
        {
            return Products != null && Products.Any() ? Products.Count().ToString() : "0";
        }
    }
    public ViewModelProductCategory ParentCategory { get; set; }
    public IEnumerable<ViewModelProductCategory> Children { get; set; }
    public IEnumerable<ViewModelProduct> Products { get; set; }
}

I'm using AutoMapper to convert the query result to viewmodel.

Here is how I'm attempting to render:

Index-view:

@model IEnumerable<MyStore.Models.ViewModels.ViewModelProductCategory>

<ul style="list-style:none;padding-left:0px;">
    @Html.Partial("_CategoryAndProductRecursive", Model)
</ul>

_CategoryAndProductRecursive.cshtml:

@model IEnumerable<MyStore.Models.ViewModels.ViewModelProductCategory>
<ul>
@if (Model != null)
{
    foreach (var item in Model)
    {
    <li>
    @item.Title
        <ul style="list-style:none;">
        @Html.Partial("_CategoryAndProductRecursive.cshtml", item.Children)
        @if (item.Products != null)
        {
            @foreach (var product in item.Products)
            {
                <li>@product.Title</li>
            }
        }
        </ul>
    </li>
    }
}
</ul>

2 个答案:

答案 0 :(得分:2)

EF Core查询部分实际上有两个独立的问题。

首先是如何热切加载ProductCategory.ProductInCategoryProductInCategory.Product导航属性。答案很简单 - 使用Include / ThenInclude`,如文档的Loading Related Data - Eager Loading 部分所述。

第二个是如何递归ProductCategory.Children。诀窍是强制在内存中加载整个 ProductCategory树,从而让EF Core导航属性修复进行父/子实体链接,然后然后过滤根实体。

var categories = _context.ProductCategories
    .Include(e => e.ProductInCategory)
        .ThenInclude(e => e.Product)
    .AsEnumerable() // <-- Force full execution (loading) of the above
    .Where(e => e.ParentId == null) // <-- then apply the root filter
    .ToList();

请注意,我们不需要在此过程中加入Children。请注意,有些Children个集合将为null。如果这是一个问题,请确保在构造函数或初始化程序中初始化它们。

public ICollection<ProductCategory> Children { get; set; } = new List<ProductCategory>();

答案 1 :(得分:0)

你可以尝试使用Load而不是Include并循环预定次数来每次加载孩子。

https://msdn.microsoft.com/en-us/library/bb896249.aspx