Avoid NullReferenceException when accessing EF navigation properties

Many of the errors that I have corrected recently are the result of null references when accessing the navigation properties of objects loaded using the entity. I believe that there should be a flaw in the way I develop my methods. Here is an example ...

The task contains many roles, each role refers to the 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; } 

Given that I would request my context like this by mistake and not load the 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 created this method with every intention of loading roles and user, but the next time I use it, I can forget that I need both. Ideally, a method definition should tell me what data is needed, but even if I pass both tasks and roles, I still miss Roles-> User.

What is the correct way to refer to these relationships and make sure they are loaded into something like this printing method? I'm interested in the best design, so "Use Lazy Loading" is not the answer I'm looking for.

Thanks!

EDIT:

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

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

I want to know how I can create my method so that when I return and use it after 6 months, I know what data needs to be loaded into my entities? The definition of a method does not indicate what is needed to use it. Or how to block these NullReferences. There must be a better design.

+7
source share
3 answers

Very good question. Here are some possible solutions that, although they do not provide NRE avoidance, they will provide a hint to the caller that they need Include things:

The first option is to ensure that your method does not gain access to the object’s not guaranteed property rather, make the caller pass both objects:

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

Another option is to name the parameter of your method so that it determines the caller with respect to what is required:

 public void PrintTask(Task taskWithUser) { // ... } 
+1
source

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

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

Edit:

There are several ways I can think to avoid NRE

  • Integration test using SQL Server CE / Express database. Unit testing with fake contexts will not work correctly.
  • Loading objects close to where they are used. So Include is next to where the entities are used.
  • Transferring DTO / ViewModels to higher levels without going through legal entities.
+2
source

User should be lazily loaded into your loop - just note that this is classic select N + 1 , which you have to fix using another Include ,

I think the root problem is that this particular Role does not have a User or that this particular Role User is null for its Name , you will need to check both null values ​​in the loop

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

All Articles