首页 > 解决方案 > c#中如何识别所有可能导致NullReferenceException的代码块

问题描述

我在 .NET 4.7.2 中使用 C# 和 Entity Framework 的项目之一会导致NullReferenceException多种情况。代码库很大,因此在使用前分析代码和处理可为空的对象非常困难。

大多数异常发生在我们尝试使用实体框架访问给定实体的相关空实体或空实体集合(通过外键相关)时。如果外键表中不存在数据,则该相关实体将存在或为空。

我们假设相关对象过去必须存在,但现在大多数表的结构已更改,这些对象现在可能不存在。

我的代码太长而且很难为可空对象处理进行分析和更改。

那么有人可以向我建议一个解决方案,它会给我一个NullReferenceException将来会导致的代码块吗?

有什么工具可以警告我将在哪里NullReferenceException发生?

标签: c#entity-frameworknullreferenceexceptioncode-analysis

解决方案


NullReferenceException是您从第一天开始就必须考虑的事情。假设总是会设置引用来编写代码,这给错误敞开了大门,未来通常会出现间歇性错误。AFAIK 没有针对该问题的“快速解决方案”,但是您可以采取重构步骤来减少 Null 的影响,并帮助确保在调用堆栈实际上可能有用的地方发生意外 null 值的异常:

#1。从 Linq 表达式中删除所有*OrDefault()未明确处理数据可能不会返回的事实的方法。这意味着在您期望 1 条记录的地方替换FirstOrDefault()First()或更好。Single()您仍然会遇到异常,但它将在读取实体时明确哪些条件无效,而稍后在使用实体而不检查#null 的代码中。

#2。初始化实体中的集合属性。这有助于确保在创建新实体时实体中的集合“准备就绪”,以防接收实体的函数可能会获得新行而不是现有行。IE

public virtual ICollection<OrderLine> OrderLines { get; set; } = new List<OrderLine>();

#3。确保启用延迟加载,并将引用和集合标记为virtual. 这绝不是理想的,但恕我直言,延迟加载调用比NullReferenceException. 如果调用碰巧是在 DbContext 范围之外进行的,那么您仍然会收到异常,但通常更容易确定访问的内容。

#4。添加快速失败验证。在接受存储的构造函数参数时,添加空检查断言。对于接受任何可能为空的方法,添加断言。您将获得例外,但有意义的例外将帮助您确定错误的来源。例如:

// Constructor injected references:
oublic OrderService(IOrderRepository repository)
{
    // change this:
    // _repository = repository;

    // ... to this:
    _repository = repository ?? throw new ArgumentNullException("repository");
}

// Parameter assertion:

public void AddCustomer(Customer customer)
{   
    // add these to assert your parameters.
    if(customer == null) throw new ArgumentNullException("customer");

    // ...
}

这些通常是快速重构,可以在整个现有应用程序中非常隐蔽地应用。

#5。旨在消除任何构建实体的代码,而不是插入新实体。我遇到的常见示例如下:

.Select(x => new Order { /* populating some Order columns */ })

实体类在哪里Order,但被用作视图模型或其他一些临时数据容器。任何其他代码,new除了用于使用之外的实体context.Orders.Add(order);应该带着偏见重新考虑。实体类应始终反映数据的完整或可完成表示。(可完成意味着被跟踪的实体仍在其 DbContext 范围内,并且能够在需要时进行延迟加载)任何被调用的接受 Order 实体的函数都应该期望接收到一个完整且有效的 Order,其中所有引用要么是预先加载的,要么是延迟加载的可加载。传递的所有实体也应该都关联到同一个 DbContext,因此在理想情况下,应该重构进出 Web 客户端的序列化实体的代码以使用投影,或者非常小心地确保实体重新关联到当前作用域的 DbContext。

最终,您面临的是一种技术债务,就像所有债务一样,您在通过假设节省开发时间方面获得的“信用”会产生利息,这将增加以后追踪错误并最终“修复”错误假设的时间永远。您需要以童子军的观点看待应用程序,并力求使其比开始时更干净。


推荐阅读