访问EF导航属性时,请避免NullReferenceException

时间:2011-12-20 01:52:38

标签: c# .net entity-framework

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

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

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;
}

考虑到我会错误地查询我的上下文并且没有加载用户 ......

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
    }
}

我可能已经构建了这个方法,每个意图加载角色和用户,但下次我使用它时,我可能会忘记我需要两者。理想情况下,方法定义应该告诉我哪些数据是必要的,但即使我传入任务和角色,我仍然缺少角色 - &gt;用户。

引用这些关系的正确方法是什么,并确保它们是像这种打印方法一样加载的?我对更好的设计感兴趣,所以“使用延迟加载”不是我正在寻找的答案。

谢谢!

编辑:

我知道我可以加载这样的任务......

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

我想知道的是我如何设计我的方法,以便当我回来并在6个月后使用它时,我知道需要在我的实体中加载哪些数据?方法定义未指出使用它的必要条件。或者我如何阻止这些NullReferences。必须有一个更好的设计。

3 个答案:

答案 0 :(得分:2)

您可以使用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靠近使用实体的地方。
  • 将DTO / ViewModel传递给上层而不传递 实体。

答案 1 :(得分:1)

User应该在你的循环中延迟加载 - 只是注意这是一个经典的select N + 1问题,你应该用另一个Include修复。

我认为根本问题是这个特定的Role没有拥有一个User,或者这个特定的Role&#39 ; User为其Name设置了空值。您需要在循环中检查两者是否为空

foreach(var r in task.Roles)
{
    if (r.User != null)
        Console.WriteLine(r.User.Name ?? "Name is null"); 
}

答案 2 :(得分:1)

非常好的问题。以下是一些可能的解决方案,虽然他们不会强制避免使用NRE,但他们会向调用者提供他们Include事物所需的线索:

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

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

另一种选择是命名方法的参数,以便它可以使调用者了解所需的内容:

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