如何在 Entity Framework Core 中调试和修复“可空对象必须具有值”?

Sup*_*JMN 11 .net c# nullable 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;
}
Run Code Online (Sandbox Code Playgroud)

我在第一行收到一个异常说

Microsoft.EntityFrameworkCore.Query:错误:迭代上下文类型“SampleApp.EF.MyDbContext”的查询结果时发生异常。System.InvalidOperationException: Nullable 对象必须有一个值。

我检查了异常和堆栈跟踪,但没有说明哪个是有问题的对象。不知道出了什么问题。

那么,有没有一种方法可以知道哪个属性会产生冲突?我确定这是属性列不匹配,但是如果您有很多属性怎么办?相当痛苦!

更新

我已经解决了这个问题。这是由于尝试将 null 分配给我的模型中不可为 null 的属性。

也就是说,这个问题不是关于这个具体的修复,而是关于找到一种通用的方法来调试这种非常常见的情况(这个异常)。恕我直言,异常消息太模糊了。它不提供冲突属性的任何有用信息。

Man*_*ari 7

可以有两个答案。

首先,这可能是因为您的查询返回 NULL 而模型不接受 null。

第二个解决方法可能是,如果您使用匿名类型,则尝试将查询结果类型转换为可空类型。

例如

Id = (int?) op.Id,
Run Code Online (Sandbox Code Playgroud)

如何找到哪个属性返回NULL?

您可以启用 SQL Profiler 并检查正在执行的 SQL 查询。将查询复制到 SSMS 中并查看哪些值为 NULL。
这可能有助于您确定是否真的需要可为空类型转换或您想要更改查询本身。

此页面上的 GitHub上存在问题。

更新:2021 年 7 月 5 日:如何查找返回 NULL 的属性?

您可以通过调用EnableDetailedErrors方法使用简单的日志记录功能(在 EF 核心 5.0 中引入)。下面的代码示例显示了如何配置 Simple Logging 以在控制台上显示日志,它还启用了详细的错误。如果你不想在控制台上显示日志,那么你可以在 LogTo 调用中传递一个动作委托,它接受一个字符串作为参数。然后,此委托可以将日志写入您选择的任何目的地。

有关更多详细信息,请参阅文档。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine)
        .EnableDetailedErrors();
Run Code Online (Sandbox Code Playgroud)

  • 获取SQL的一种更简单的方法是将查询结果的赋值分为两个步骤。第一个是描述查询的任务 - 将其分配给临时变量。然后,作为第二步,将最终结果(在临时结果上等待调用“FirstOrDefaultAsync”的结果)分配给您将返回的变量。调试你的程序,逐步完成这一切。在调用“FirstOrDefaultAsync”*之后*检查您的临时变量,您应该在其中一个属性中看到 SQL (3认同)

K0D*_*0D4 6

我想添加到@Manoj-Choudhari 的回答中,这有助于我追踪从 EF Core 3.x 升级到 EF Core 5.x 后开始的问题。

我有一个这样的查询:(Item[1] -> Categories[0..n] -> Parent[1],keys are ints)

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
}
Run Code Online (Sandbox Code Playgroud)

如果导航到“父级”最终为空,则此操作将失败并显示相同的“可空对象必须具有值”错误。我观察到的 EFC3 和 EFC5 之间行为的不同之处在于投影的 parent.Id 部分曾经被自动推断为 int?因为它似乎明白 parent 可能为 null。似乎您必须更明确地使用 EF Core 5,并实际手动将映射类型转换为可空类型。

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
}
Run Code Online (Sandbox Code Playgroud)

旧的方式可能更像是一种利用,现在最好是具体的。

编辑: EF Core 团队知道这种行为:https : //github.com/dotnet/efcore/issues/22517


小智 5

通常,据我所知,它没有更快的方法来找到造成问题的确切字段。

根据您的情况,您可以做的是:

1 - 查看您正在执行的查询,并验证它是否返回所需的数据。如果一切顺利,那么查询可能会返回 null,并且您的模型不接受该值(反之亦然)。

2 - 如果第一项工作正常,请尝试查看您在模型上进行的其他迭代。例如:您正在执行 Positions.Select(),因此请验证职位以及来自职位模型的其他内容是否都正常。

3 - 如果您不能百分百确定该领域是什么。只需为您正在执行 linq 的每个字段添加一个可以为 null 的类型即可。示例:BrokerId = trade.EvgTGBrokerTrade.FirstOrDefault().IdBroker,您可以添加问号以确保该字段不会导致您的应用程序崩溃。

4 - 查看您在数据库上创建的表,如果字段可为空或不可为空,它可能会包含字段的信息。然后,您可以更改您的代码模型。

这些是我为你建议的事情

  • 从那时起,这种情况有任何改善吗?仍然只收到消息“可空对象必须有一个值”,并且在尝试使用长查询调试代码时不知道它是什么字段。 (2认同)