You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

递归读取树形结构Linq查询报错:ArgumentNullException排查求助

解决Linq查询中的ArgumentNullException及树形结构展示问题

首先,咱们来拆解你遇到的几个核心问题:

1. 空引用异常的直接原因

你在OrphanProducts的查询里调用了v.ProductInCategory.Where(...),但如果某个分类没有关联任何产品,v.ProductInCategory会是null(因为实体里定义的是List<ProductInCategory>,EF默认不会初始化空列表),这时候调用Where就会抛出ArgumentNullException。

2. 孤儿产品的查询逻辑错误

你当前的查询逻辑是从当前分类的产品关联记录里找“不在任何产品关联记录里的产品”,这本身就是矛盾的——孤儿产品应该是没有任何ProductInCategory关联记录的产品,和某个具体分类无关。

3. 树形视图的类型不匹配问题

你的递归视图_CategoryRecursive接收的是IEnumerable<ViewModelProductCategory>,但你在构建ViewModel时直接把实体的v.Children(类型是IEnumerable<ProductCategory>)赋值给了ViewModelProductCategory.Children,这会导致视图渲染时类型不匹配,后续肯定会报错。


修正后的完整解决方案

第一步:先单独查询孤儿产品

把孤儿产品的查询和分类查询分开,逻辑更清晰,也避免重复查询:

// 获取所有孤儿产品(没有任何分类关联的产品)
var orphanProducts = _context.Products
    .Where(p => !_context.ProductsInCategories.Any(pc => pc.ProductId == p.Id))
    .Select(p => new ViewModelProduct {
        Id = p.Id,
        Title = p.Title,
        Price = p.Price,
        Info = p.Info,
        SortOrder = 0 // 孤儿产品可以设一个默认排序值
    })
    .OrderBy(s => s.SortOrder)
    .ToList();

第二步:递归构建分类ViewModel

因为要生成树形结构,我们需要一个递归方法来把实体ProductCategory转换成ViewModelProductCategory:

// 递归转换分类实体到ViewModel
private ViewModelProductCategory MapToViewModel(ProductCategory category)
{
    if (category == null) return null;

    // 获取当前分类下的产品,处理null情况
    var categoryProducts = category.ProductInCategory?
        .Select(pic => new ViewModelProduct {
            Id = pic.Product.Id,
            Title = pic.Product.Title,
            Price = pic.Product.Price,
            Info = pic.Product.Info,
            SortOrder = pic.SortOrder
        })
        .OrderBy(s => s.SortOrder)
        .ToList() ?? new List<ViewModelProduct>();

    return new ViewModelProductCategory {
        Id = category.Id,
        ParentId = category.ParentId,
        Title = category.Title,
        SortOrder = category.SortOrder,
        Products = categoryProducts,
        // 递归转换子分类,处理null情况
        Children = category.Children?
            .OrderBy(c => c.SortOrder)
            .Select(MapToViewModel)
            .ToList() ?? new List<ViewModelProductCategory>()
    };
}

第三步:构建最终的分类ViewModel集合并赋值孤儿产品

// 获取根分类并转换为ViewModel,注意Include所有关联数据
var rootCategories = _context.ProductCategories
    .Include(e => e.Children)
    .Include(e => e.ProductInCategory)
        .ThenInclude(pic => pic.Product) // 必须Include关联的Product,否则会触发懒加载或空引用
    .Where(r => r.ParentId == null)
    .OrderBy(s => s.SortOrder)
    .Select(MapToViewModel)
    .ToList();

// 把孤儿产品作为特殊根分类添加进去(也可以通过ViewBag单独传递到视图)
rootCategories.Add(new ViewModelProductCategory {
    Id = -1,
    Title = "未分类产品",
    SortOrder = int.MaxValue,
    OrphanProducts = orphanProducts
});

第四步:修正递归视图的类型匹配问题

你的递归视图现在接收的是IEnumerable<ViewModelProductCategory>,所以在渲染子分类时要传递item.Children(现在已经是ViewModel集合了):

@model IEnumerable<MyStore.Models.ViewModels.ViewModelProductCategory>
<ul style="list-style:none;padding-left:20px;">
    @if (Model != null) {
        foreach (var item in Model.OrderBy(c => c.SortOrder)) {
            <li>
                <h3>@item.Title (@item.ProductCountInfo)</h3>
                
                <!-- 显示当前分类的产品 -->
                @if (item.Products.Any()) {
                    <ul>
                        @foreach (var product in item.Products) {
                            <li>@product.Title - @product.Price</li>
                        }
                    </ul>
                }
                
                <!-- 显示孤儿产品(如果是特殊分类的话) -->
                @if (item.OrphanProducts != null && item.OrphanProducts.Any()) {
                    <h4>未分类产品</h4>
                    <ul>
                        @foreach (var product in item.OrphanProducts) {
                            <li>@product.Title - @product.Price</li>
                        }
                    </ul>
                }
                
                <!-- 递归渲染子分类 -->
                @Html.Partial("_CategoryRecursive", item.Children)
            </li>
        }
    }
</ul>

额外注意事项

  • 一定要确保EF查询中Include了所有需要的关联数据(比如ProductInCategory和对应的Product),避免懒加载导致的空引用或性能问题。
  • 实体类中的集合属性建议初始化为空列表(比如public List<ProductInCategory> ProductInCategory { get; set; } = new List<ProductInCategory>();),这样即使没有关联数据也不会为null,从根源避免空引用。
  • 孤儿产品的展示逻辑可以根据你的需求调整:比如单独放在视图顶部,或者作为一个特殊分类节点,上面的代码示例是后者的实现方式。

内容的提问来源于stack exchange,提问作者Stian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:00:42