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.
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
Include
s are near to where the entities are used. 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");
}
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)
{
// ...
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.