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
相关产品推荐
相关产品推荐

