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

C# Linq LeftJoin报Nullable object must have a value错误

问题描述

在使用LINQ编写左连接查询逻辑时,Select投影操作中访问变量y会抛出Nullable object must have a value异常,代码中已经对y做了非空判断但异常依然触发,问题代码如下:

var products = productQuery
        .GroupJoin(customerProductPrices,
        p => p.Id,
        pp => pp.ProductId,
                 (p, pp) => new { Product = p, CustomerProductPrice = pp })
                .SelectMany(
                   x => x.CustomerProductPrice.DefaultIfEmpty(),
                   (x, y) => new ProductFilterResultModel
                   {
                      Id = x.Product.Id,                           
                       Price = y != null  ? y.Price : 0
                   });
异常产生原因
  • 首先修正原代码笔误:productQuery.后多了一个冗余的点,会直接触发编译错误,需要先删除。
  • 核心问题出在ORM(EF/EF Core等)场景下DefaultIfEmpty()的映射逻辑:当左连接没有匹配到右表数据时,ORM不会在C#层面返回null的右表实例,而是生成一个所有字段取默认值的占位实例,代码中写的y != null判断对这种非null的占位实例完全不生效。
  • 当代码执行到投影逻辑时,ORM会尝试访问占位实例的属性做映射,如果属性是Nullable<T>类型,因为没有真实数据支撑Value属性取值,就会直接抛出Nullable object must have a value异常,根本不会走到编写的三元判断分支。
  • 注意:如果是对内存集合执行LINQ to Object查询,y != null的判断可以正常生效,该异常只会出现在需要将LINQ翻译为SQL执行的数据库查询场景。
解决方法
  • 方案1:避开DefaultIfEmpty的空判断陷阱,直接在GroupJoin结果中取关联集合的首条记录,让ORM自动翻译空值逻辑,兼容性最好:
var products = productQuery
    .GroupJoin(customerProductPrices,
        p => p.Id,
        pp => pp.ProductId,
        (p, pp) => new { Product = p, CustomerProductPrices = pp })
    .Select(x => new ProductFilterResultModel
    {
        Id = x.Product.Id,
        Price = x.CustomerProductPrices.Select(pp => pp.Price).FirstOrDefault()
    })
    .ToList();
  • 方案2:保留SelectMany的写法,将非空判断从“判断y本身是否为null”改为“判断右表主键是否为类型默认值”,适配老版本EF:
var products = productQuery
    .GroupJoin(customerProductPrices,
        p => p.Id,
        pp => pp.ProductId,
        (p, pp) => new { Product = p, CustomerProductPrice = pp })
    .SelectMany(
        x => x.CustomerProductPrice.DefaultIfEmpty(),
        (x, y) => new ProductFilterResultModel
        {
            Id = x.Product.Id,
            // 此处以int类型主键为例,判断主键是否为默认值0
            Price = y.ProductId != 0 ? y.Price : 0
        })
    .ToList();

如果右表主键是Guid等其他值类型,将判断条件替换为对应主键类型的默认值比较即可。

  • 方案3:EF Core 3.0及以上版本可以直接使用内置的LeftJoin扩展方法,从语法层面规避空值映射问题:
var products = productQuery
    .LeftJoin(
        customerProductPrices,
        p => p.Id,
        pp => pp.ProductId,
        (p, pp) => new ProductFilterResultModel
        {
            Id = p.Id,
            Price = pp != null ? pp.Price : 0
        })
    .ToList();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 14:24:25