.NET 6升级后EF Core报Nullable object must have a value问题求全局解决
问题背景
在.NET Core 3.1中运行正常的查询表达式:
var result = _dbContext.Orders.Where(x => x.OrderId == orderId) .Select(x => new { x.Customer.CustomerId, x.ProductId, x.Quantity, }).FirstOrDefault();
升级至.NET 6及EF Core 6.x后,出现Nullable object must have a value错误,原因是部分Order记录的Customer导航属性为null,访问x.Customer.CustomerId时触发异常。手动将x.Customer.CustomerId强转为可空类型(int?)x.Customer.CustomerId可解决问题,但现有数百处类似代码,批量修改成本高。
一、导致语句失效的核心变更
Nullable Reference Types(NRT)默认启用
.NET 6开始默认在项目中启用Nullable参考类型(通过项目文件<Nullable>enable</Nullable>配置),EF Core 6对模型中导航属性的空值校验更严格。当导航属性被标记为非可空(未加?)但实际数据库中存在null值时,EF Core会在查询投影时触发空值异常,而EF Core 3.1中这类场景会隐式忽略空值或在数据库层面返回null,不会抛出异常。EF Core 6查询投影的空值处理逻辑变更
EF Core 6优化了查询翻译和客户端投影的处理逻辑,对于非可空导航属性的属性访问,不再自动将null转换为可空类型。如果导航属性实际为null,访问其非可空属性会直接抛出InvalidOperationException,也就是你遇到的"Nullable object must have a value"错误。
二、全局/模型层面的解决方案
1. 修正模型中导航属性的可空性标记
这是最推荐的方案,从根源上匹配数据库的实际数据情况:
- 如果Order表允许Customer为null(外键CustomerId可空),将Order实体中的导航属性和外键字段标记为可空:
修正模型后,EF Core会自动在投影时将public class Order { public int OrderId { get; set; } public int? CustomerId { get; set; } // 外键字段设为可空 public Customer? Customer { get; set; } // 导航属性设为可空 public int ProductId { get; set; } public int Quantity { get; set; } }x.Customer.CustomerId处理为可空类型,无需手动强转,原有查询代码可正常运行。
2. 全局关闭Nullable参考类型(不推荐)
如果暂时无法修改模型,可通过项目文件全局关闭NRT:
在项目的.csproj文件中修改配置:
<PropertyGroup> <Nullable>disable</Nullable> </PropertyGroup>
此方法能快速解决异常,但会失去NRT带来的编译期空值校验能力,可能引入潜在的空引用问题,仅作为临时应急方案。
3. 全局配置EF Core导航属性的可空性
在DbContext的OnModelCreating方法中,批量设置所有实体的导航属性为可空,无需逐个实体调整:
protected override void OnModelCreating(ModelBuilder modelBuilder) { foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { foreach (var navigation in entityType.GetNavigations()) { // 将所有引用类型的导航属性标记为非必填(可空) if (navigation.ClrType.IsClass) { navigation.SetIsRequired(false); } } } }
配置后,EF Core会自动适配空导航属性的投影场景,原有查询代码无需修改即可运行。
内容的提问来源于stack exchange,提问作者Efthymios Kalyviotis

