Avoid NullReferenceException when accessing EF Navigation Properties

Many of the bugs I've been fixing lately are a result of null references when accessing navigation properties of objects loaded using entity framework. I believe there must be a flaw in how I'm designing my methods. Here's an example...

A Task contains Many Roles, each Role references a User.

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

Considering that I would have queried my context like this by mistake and not loaded User ...

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

And then I call this method...

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

I may have built this method with every intention to load Roles and User but next time I use it I may forget that I need both. Ideally the method definition should tell me what data is necessary, but even if I pass in both Task and Roles, I'm still missing Roles->User.

What's the proper way to reference these relationships and be sure that they're loaded in something like this print method? I'm interested in a better design, so "Use Lazy Loading" isn't the answer I'm looking for.

Thanks!

EDIT:

I know I can load the task like this...

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

What I want to know is how do I design my method so that when I come back and use it 6 months from now I know what data needs to be loaded in my entities? The method definition doesn't indicate what is necessary to use it. Or how to I block against these NullReferences. There has to be a better design.


Very good question. Here are some possible solutions that, while they don't enforce the avoidance of NREs, they'll provide clues to the caller that they need to Include things:

The first option is to not have your method access a non-guaranteed property of an entity; rather, force the caller to pass both entities:

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

Another option is to name the parameter of your method such that it will clue the caller as to what is required:

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

You can use the Select extension method to eager load Users .

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

Edit:

There are few ways that I can think of to avoid the NRE

  • Integration test using a SQL Server CE/Express database. Unit testing with fake contexts will not work correctly.
  • Loading the entities close to where they are consumed. So that the Include s are near to where the entities are used.
  • Passing DTOs/ViewModels to the upper layers without passing the entities.

  • User should be lazily loaded in your loop—just note though that this is a classic select N + 1 problem that you should fix with another Include .

    I think the root problem is either that this particular Role doesn't have a User , or that this particular Role 's User has null set for its Name . You'll need to check both for null in your loop

    foreach(var r in task.Roles)
    {
        if (r.User != null)
            Console.WriteLine(r.User.Name ?? "Name is null"); 
    }
    
    链接地址: http://www.djcxy.com/p/56842.html

    上一篇: 停止CMD始终以管理员权限打开

    下一篇: 访问EF导航属性时避免出现NullReferenceException