如何在 Entity Framework 内核中调试和修复 'Nullable object must have a value'?
How to debug and fix 'Nullable object must have a value' within Entity Framework Core?
我正在用这种方法进行投影:
public async Task<TradeDetail> Get(int tradeId)
{
var firstOrDefaultAsync = await context.EvgTGTrade
.Where(x => x.IdTrade == tradeId)
.Select(trade => new TradeDetail
{
Id = trade.IdTrade,
Code = trade.CdTrade,
Description = trade.DeTrade,
Comments = trade.DeComentarios,
TypeId = trade.IdTipoTrade,
BrokerId = trade.EvgTGBrokerTrade.FirstOrDefault().IdBroker,
BillingCycleId = trade.IdCicloFacturacion,
CounterpartId = trade.IdSujeto,
StartDate = trade.FhIni,
EndDate = trade.FhFin,
SignatureDate = trade.FhFirma,
Positions = trade.EvgTGPosicion.Select(pos => new Position
{
Amount = pos.Cantidad,
Code = pos.CdPosicion,
Description = pos.DePosicion,
LogisticElement = pos.IdElemLogNavigation.DeElemLog,
StartDate = pos.FhIni,
EndDate = pos.FhFin,
FormulaId = pos.IdFormula,
Operations = pos.EvgTGOperacionCv.Select(op =>
new Operation()
{
TypeId = op.IdTipoOperacion,
Fee = op.Fee,
Date = op.FhOperacionCv,
Price = op.NmPrecio ?? 0,
Quantity = op.NmCantidadKwh ?? 0,
}),
})
})
.FirstOrDefaultAsync();
return firstOrDefaultAsync;
}
第一行出现异常
Microsoft.EntityFrameworkCore.Query: Error: An exception occurred while iterating over the results of a query for context type 'SampleApp.EF.MyDbContext'.
System.InvalidOperationException: Nullable object must have a value.
我检查了异常和堆栈跟踪,但没有说明哪个是有问题的对象。不知道哪里出了问题。
那么,有没有一种方法可以知道哪个 属性 产生了冲突?我确定这是 属性 列不匹配,但是如果您有很多属性怎么办?好痛啊!
更新
我已经解决了这个问题。这是由于尝试将 null 分配给我的模型中不可为 null 的 属性。
就是说,这个问题不是关于这个具体的修复,而是关于找到一种通用的方法来调试这种情况(这个例外),这是很常见的。恕我直言,异常消息太模糊了。它没有提供冲突 属性.
的任何有用信息
可以有两个答案。
首先,这可能是因为您的查询返回 NULL 而模型不接受 null。
第二个修复可能是,如果您使用的是匿名类型,则尝试将查询结果类型转换为可空类型。
例如
Id = (int?) op.Id,
如何找到哪个 属性 返回 NULL?
您可以启用 SQL Profiler 并检查正在执行的 SQL 查询。将查询复制到 SSMS 并查看哪些值为 NULL。
这可能有助于您决定是否真的需要可空类型转换或者您想要更改查询本身。
GitHub on this page 上有问题。
更新:2021 年 7 月 5 日:如何找到哪个 属性 返回 NULL?
您可以通过调用 EnableDetailedErrors
方法使用简单的日志记录功能(在 EF core 5.0 中引入)。
下面的代码示例显示了如何配置简单日志记录以在控制台上显示日志,它还启用详细错误。
如果你不想在控制台上显示日志,那么你可以在 LogTo 调用中传递一个动作委托,它接受一个字符串作为参数。然后,该代表可以将日志写入您选择的任何目的地。
参考documentation了解更多详情。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableDetailedErrors();
据我所知,通常没有更快的方法来找到导致问题的确切字段。
根据您的案例,您可以做的是:
1 - 查看您正在执行的查询,并验证它是否 returning 所需的数据。如果它发生得很好,那么查询可能 return 为空,并且您的模型不接受它(反之亦然)。
2 - 如果第一项工作正常,请尝试查看您在模型上进行的其他迭代。例如:您正在执行 Positions.Select(),因此验证位置以及来自位置模型的其他内容是否都正常。
3 - 如果您不能百分百确定该字段是什么。只需为您正在执行 linq 的每个字段放置一个可空类型。示例:BrokerId = trade.EvgTGBrokerTrade.FirstOrDefault().IdBroker,您可以添加一个问号以确保该字段不会使您的应用程序崩溃。
4 - 查看您在数据库上创建的表,如果它们是否可为空,它可能会包含字段的信息。然后,您可以更改您的代码模型。
这些是我为您推荐的东西
在我的例子中,我必须更改基于枚举的属性的 LINQ 语句
private readonly Enum _value;
来自
x => x.EnumProperty == _value;
至
x => x.EnumProperty.Equals(_value);
不太明白为什么,因为我的逻辑在使用旧 Linq 的应用程序中工作正常,但测试失败,直到我使用 .Equals。环境的唯一区别是 MSSqlDb 与 InMemoryDb
我想补充@Manoj-Choudhari 的回答,它帮助我找到了一个问题,这个问题是在我从 EF Core 3.x 升级到 EF Core 5.x 后开始的。
我有这样的查询:(Item[1] -> Categories[0..n] -> Parent[1],键是整数)
from item in ctx.Items
from ixc in item.Categories.DefaultIfEmpty() /* Left join */
let parent = ixc.Parent
select new {
itemId = item.Id,
parentId = parent.Id
}
如果到“父级”的导航最终为空,则此操作失败并出现相同的“可空对象必须具有值”错误。我观察到 EFC3 和 EFC5 之间行为的不同之处在于投影的 parent.Id 部分曾经被自动推断为 int?因为它似乎理解 parent 可以为空。似乎您必须更明确地使用 EF Core 5,并实际将映射类型手动转换为可为 null 的类型。
from item in ctx.Items
from ixc in item.Categories.DefaultIfEmpty() /* Left join */
let parent = ixc.Parent
select new {
itemId = item.Id,
parentId = (int?)parent.Id
}
旧方法可能更像是一种利用,现在最好具体一些。
编辑:
EF Core 团队知道此行为:https://github.com/dotnet/efcore/issues/22517
对于那个和其他一些 ef 异常,我经常使用 https://www.linqpad.net/
- 在linqpad中设置数据库连接
- 复制问题查询
- 执行
- 如果发现错误,请删除部分查询并重复第 3 步
一旦我找到导致问题的查询部分,通常很容易找到解决方法。
我正在用这种方法进行投影:
public async Task<TradeDetail> Get(int tradeId)
{
var firstOrDefaultAsync = await context.EvgTGTrade
.Where(x => x.IdTrade == tradeId)
.Select(trade => new TradeDetail
{
Id = trade.IdTrade,
Code = trade.CdTrade,
Description = trade.DeTrade,
Comments = trade.DeComentarios,
TypeId = trade.IdTipoTrade,
BrokerId = trade.EvgTGBrokerTrade.FirstOrDefault().IdBroker,
BillingCycleId = trade.IdCicloFacturacion,
CounterpartId = trade.IdSujeto,
StartDate = trade.FhIni,
EndDate = trade.FhFin,
SignatureDate = trade.FhFirma,
Positions = trade.EvgTGPosicion.Select(pos => new Position
{
Amount = pos.Cantidad,
Code = pos.CdPosicion,
Description = pos.DePosicion,
LogisticElement = pos.IdElemLogNavigation.DeElemLog,
StartDate = pos.FhIni,
EndDate = pos.FhFin,
FormulaId = pos.IdFormula,
Operations = pos.EvgTGOperacionCv.Select(op =>
new Operation()
{
TypeId = op.IdTipoOperacion,
Fee = op.Fee,
Date = op.FhOperacionCv,
Price = op.NmPrecio ?? 0,
Quantity = op.NmCantidadKwh ?? 0,
}),
})
})
.FirstOrDefaultAsync();
return firstOrDefaultAsync;
}
第一行出现异常
Microsoft.EntityFrameworkCore.Query: Error: An exception occurred while iterating over the results of a query for context type 'SampleApp.EF.MyDbContext'. System.InvalidOperationException: Nullable object must have a value.
我检查了异常和堆栈跟踪,但没有说明哪个是有问题的对象。不知道哪里出了问题。
那么,有没有一种方法可以知道哪个 属性 产生了冲突?我确定这是 属性 列不匹配,但是如果您有很多属性怎么办?好痛啊!
更新
我已经解决了这个问题。这是由于尝试将 null 分配给我的模型中不可为 null 的 属性。
就是说,这个问题不是关于这个具体的修复,而是关于找到一种通用的方法来调试这种情况(这个例外),这是很常见的。恕我直言,异常消息太模糊了。它没有提供冲突 属性.
的任何有用信息可以有两个答案。
首先,这可能是因为您的查询返回 NULL 而模型不接受 null。
第二个修复可能是,如果您使用的是匿名类型,则尝试将查询结果类型转换为可空类型。
例如
Id = (int?) op.Id,
如何找到哪个 属性 返回 NULL?
您可以启用 SQL Profiler 并检查正在执行的 SQL 查询。将查询复制到 SSMS 并查看哪些值为 NULL。
这可能有助于您决定是否真的需要可空类型转换或者您想要更改查询本身。
GitHub on this page 上有问题。
更新:2021 年 7 月 5 日:如何找到哪个 属性 返回 NULL?
您可以通过调用 EnableDetailedErrors
方法使用简单的日志记录功能(在 EF core 5.0 中引入)。
下面的代码示例显示了如何配置简单日志记录以在控制台上显示日志,它还启用详细错误。
如果你不想在控制台上显示日志,那么你可以在 LogTo 调用中传递一个动作委托,它接受一个字符串作为参数。然后,该代表可以将日志写入您选择的任何目的地。
参考documentation了解更多详情。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableDetailedErrors();
据我所知,通常没有更快的方法来找到导致问题的确切字段。
根据您的案例,您可以做的是:
1 - 查看您正在执行的查询,并验证它是否 returning 所需的数据。如果它发生得很好,那么查询可能 return 为空,并且您的模型不接受它(反之亦然)。
2 - 如果第一项工作正常,请尝试查看您在模型上进行的其他迭代。例如:您正在执行 Positions.Select(),因此验证位置以及来自位置模型的其他内容是否都正常。
3 - 如果您不能百分百确定该字段是什么。只需为您正在执行 linq 的每个字段放置一个可空类型。示例:BrokerId = trade.EvgTGBrokerTrade.FirstOrDefault().IdBroker,您可以添加一个问号以确保该字段不会使您的应用程序崩溃。
4 - 查看您在数据库上创建的表,如果它们是否可为空,它可能会包含字段的信息。然后,您可以更改您的代码模型。
这些是我为您推荐的东西
在我的例子中,我必须更改基于枚举的属性的 LINQ 语句
private readonly Enum _value;
来自
x => x.EnumProperty == _value;
至
x => x.EnumProperty.Equals(_value);
不太明白为什么,因为我的逻辑在使用旧 Linq 的应用程序中工作正常,但测试失败,直到我使用 .Equals。环境的唯一区别是 MSSqlDb 与 InMemoryDb
我想补充@Manoj-Choudhari 的回答,它帮助我找到了一个问题,这个问题是在我从 EF Core 3.x 升级到 EF Core 5.x 后开始的。
我有这样的查询:(Item[1] -> Categories[0..n] -> Parent[1],键是整数)
from item in ctx.Items
from ixc in item.Categories.DefaultIfEmpty() /* Left join */
let parent = ixc.Parent
select new {
itemId = item.Id,
parentId = parent.Id
}
如果到“父级”的导航最终为空,则此操作失败并出现相同的“可空对象必须具有值”错误。我观察到 EFC3 和 EFC5 之间行为的不同之处在于投影的 parent.Id 部分曾经被自动推断为 int?因为它似乎理解 parent 可以为空。似乎您必须更明确地使用 EF Core 5,并实际将映射类型手动转换为可为 null 的类型。
from item in ctx.Items
from ixc in item.Categories.DefaultIfEmpty() /* Left join */
let parent = ixc.Parent
select new {
itemId = item.Id,
parentId = (int?)parent.Id
}
旧方法可能更像是一种利用,现在最好具体一些。
编辑: EF Core 团队知道此行为:https://github.com/dotnet/efcore/issues/22517
对于那个和其他一些 ef 异常,我经常使用 https://www.linqpad.net/
- 在linqpad中设置数据库连接
- 复制问题查询
- 执行
- 如果发现错误,请删除部分查询并重复第 3 步
一旦我找到导致问题的查询部分,通常很容易找到解决方法。