在访问EF导航属性时避免使用NullReferenceException

本文关键字:NullReferenceException 访问 EF 导航 属性 | 更新日期: 2023-09-27 18:18:25

我最近一直在修复的许多错误都是在访问使用实体框架加载的对象的导航属性时null引用的结果。我相信我设计的方法一定有缺陷。这里有一个例子…

一个任务包含多个角色,每个角色引用一个用户。

public class Role
{
    public int Id;
    public int User_Id;
    public string Type;
}
public class User
{
    public int Id
    public string Name;
}    
public class Task
{
    public int Id;
    public string Name;
    public string Status;
    public List<Role> Roles;
}

考虑到我会像这样错误地查询我的上下文,而不是加载User…

var task = context.Tasks.Include(x=>x.Roles).FirstOrDefault;

然后我调用这个方法。

public void PrintTask(Task task)
{
    Console.WriteLine(task.Name);
    Console.WriteLine(task.Status);
    foreach(var r in task.Roles)
    {
        Console.WriteLine(r.User.Name); //This will throw NRE because User wasn't loaded
    }
}

我可能已经建立了这个方法加载角色和用户的意图,但下次我使用它时,我可能会忘记我需要这两个。理想情况下,方法定义应该告诉我需要哪些数据,但即使我同时传入Task和Roles,我仍然缺少Roles->User。

引用这些关系的正确方法是什么,并确保它们被加载到像这样的print方法中?我对更好的设计感兴趣,所以"使用延迟加载"不是我想要的答案。

谢谢!

编辑:

我知道我可以像这样加载任务…

var task = context.Tasks.Include(x=>x.Roles.Select(z=>z.User)).FirstOrDefault();

我想知道的是我如何设计我的方法,以便当我6个月后回来使用它时,我知道需要在我的实体中加载什么数据?方法定义没有指明使用它需要什么。或者如何阻止这些NullReferences。肯定有更好的设计。

在访问EF导航属性时避免使用NullReferenceException

可以使用Select扩展方法来动态加载Users

var task = context.Tasks.Include(x => x.Roles)
             .Include(x => x.Roles.Select(r => r.User))
             .FirstOrDefault();
编辑:

我能想到几个方法来避免NRE

  • 使用SQL Server CE/Express数据库进行集成测试。单元测试
  • 加载靠近它们被消耗位置的实体。所以Include s靠近使用实体的地方。
  • 传递dto/ViewModels到上层而不传递实体。

问得好。这里有一些可能的解决方案,虽然它们不强制避免NREs,但它们将为调用者提供他们需要Include事物的线索:

第一个选项是不让你的方法访问实体的非保证属性;相反,强制调用者传递两个实体:

public void PrintTask(Task task, User taskUser)
{
    // ...
}

另一个选择是命名你的方法的参数,这样它将提示调用者需要什么:

public void PrintTask(Task taskWithUser)
{
    // ...
}

User应该在循环中惰性加载-只需注意,这是一个典型的选择N + 1问题,您应该使用另一个Include来修复。

我认为根本问题是这个特定的Role没有 User,或者这个特定的RoleUser为其Name设置了空集。您需要在循环

中检查是否为空。
foreach(var r in task.Roles)
{
    if (r.User != null)
        Console.WriteLine(r.User.Name ?? "Name is null"); 
}