Tuesday, December 10, 2013

Loading Related Entities in Entity Framework

In Entity Framework Navigation properties are used to create associations/relationships among entities. Today I am going to write about the three ways which can be used to load related entities data along with the primary entity. I will not be covering the basics of Entity Framework here in this post and I assume you have some experience with Entity Framework programming model.

I am going to go by an example here, because it will be easy for you to understand and it will be easy for me to explain. I am using the Code First approach in Entity Framework. But if you are using Model First Approach, the concept will be basically the same.

I am creating a console application. There I have following two entity classes and two helper methods. The relationship among these entities are pretty much straight forward. Single Department can have many Employees working in it and a single Employee can belong to only one Department. This particular relationship is defined using two Navigation properties in two entities which are the Employees property in Department entity and the Department property in Employee entity.

Department
public class Department
{
    public int DepartmentId { get; set; }
    public string DepartmentName { get; set; }
    public ICollection<Employee> Employees { get; set; }
 
    public static List<Department> GetDepartments()
    {
        return new List<Department>()
        {
            new Department()
            {
                DepartmentId=1,
                DepartmentName="Microsoft Visual Studio"
            },
            new Department()
            {
                DepartmentId=2,
                DepartmentName="Microsoft SQL Server"
            }
        };
    }
}
Employee
public class Employee
{
    public int EmployeeId { get; set; }
    public string FirstName { get; set;}
    public string LastName { get; set; }
    public Department Department { get; set; }
 
    public static List<Employee> GetEmployees()
    {
        return new List<Employee>()
        {
            new Employee()
            {
                EmployeeId=1,
                FirstName="Jaliya",
                LastName="Udagedara",
                Department=Department.GetDepartments().First(d=>d.DepartmentId==1)
            },
            new Employee()
            {
                EmployeeId=2,
                FirstName="John",
                LastName="Smith",
                Department=Department.GetDepartments().First(d=>d.DepartmentId==2)
            },
            new Employee()
            {
                EmployeeId=3,
                FirstName="Gary",
                LastName="Smith",
                Department=Department.GetDepartments().First(d=>d.DepartmentId==1)
            }
        };
    }
}
Here in the GetEmployees() method in Employee entity, for the Employees Department, I am looking up Departments and filling up some data.

Now I am going to create a derived context which is the class MyContext and it is deriving from DbContext.
public class MyContext : DbContext
{
    public DbSet<Department> Departments { get; set; }
    public DbSet<Employee> Employees { get; set; }
}
I have two properties there which are of type DbSet which models my previously defined two entities. Now inside my Main method I am doing some data access using  MyContext. For that again I have a static helper method called AddData().
static void AddData()
{
    using (MyContext context = new MyContext())
    {
        foreach (var department in Department.GetDepartments())
        {
            context.Departments.Add(department);
        }
        context.SaveChanges(); 

        foreach (var employee in Employee.GetEmployees())
        {
            employee.Department = context.Departments.
                First(d => d.DepartmentId == employee.Department.DepartmentId);
            context.Employees.Add(employee);
        }
        context.SaveChanges();
    }
}
Here using MyContext, first I am inserting the departments returned from my helper method GetDepartments(). After saving those changes, then I am inserting the employees returned from the GetEmployees() method. Please note that now only I am creating the actual relationship between two entities.

After inserting the data, I am browsing my localdb to check the inserted data.

Picture1
Department

Picture2
Employee
Here I can see the data inserted and the relationship created between Employee and the Department.

Now let’s move into the actual topic which is querying/loading the data. Inside  my  Main method, I have the following code which should print out each employees FirstName and the DepartmentName.
using (MyContext context = new MyContext())
{
    foreach (Employee employee in context.Employees)
    {
        Console.WriteLine(employee.FirstName + "-"
            + employee.Department.DepartmentName);
    }
}

Hoping this would print out the data, If I run this code, I am getting the following error.

Picture1
Error

The error is a classic “Object reference not set to an instance of an object.”. If you debug the project you can easily find out the reason, that is the Department property  is still null in the Employee.

Untitled
Department is null
So now we know though the information in primary entity is loaded, the information from related entities are not loaded. Now let’s see how we can load information from related entities. There are three ways to achieve this which are as follows,
  1. Eagerly Loading
  2. Explicit Loading
  3. Lazy Loading

Eagerly Loading

Eagerly loading uses ObjectQuery<T>.Include Method which specifies the related objects to include in the query results (as name suggest we are specifying our keen interest on some particular related entity). So specifically we are saying load mentioned entity details along with the primary entity.
using (MyContext context = new MyContext())
{
    // eagerly loading department of the employee
    var employees = context.Employees.Include(e => e.Department).ToList();
    // or
    //var employees = context.Employees.Include("Department").ToList();
 
    foreach (Employee employee in employees)
    {
        Console.WriteLine(employee.FirstName + "-"
            + employee.Department.DepartmentName);
    }
 
    // eagerly loading employees of the department
    var departments = context.Departments.Include(d => d.Employees).ToList();
    // or
    //var departments = context.Departments.Include("Employees").ToList(); 

    foreach (Department department in departments)
    {
        Console.WriteLine(department.DepartmentName);
        Console.WriteLine("------------------");
        foreach (Employee employee in department.Employees)
        {
            Console.WriteLine(employee.FirstName);
        }
        Console.WriteLine("------------------");
    }
}
You can either use a LINQ expression or a string to mention the entity. I am always preferring the LINQ Expression over the string.

Explicit Loading

Explicit loading is accomplished through DbExtensions.Load Method. This is an extension method on IQueryable that enumerates the results of the query. This is equivalent to calling ToList without actually creating the list.
using (MyContext context = new MyContext())
{
    // explicit loading department of the employee
    foreach (Employee employee in context.Employees)
    {
        context.Entry(employee).Reference(e => e.Department).Load();
        // or
        //context.Entry(employee).Reference("Department").Load();
 
        Console.WriteLine(employee.FirstName + "-"
            + employee.Department.DepartmentName);
    }
    
    // explicit loading employees of the department
    foreach (Department department in context.Departments)
    {
        context.Entry(department).Collection(d => d.Employees).Load();
        // or
        //context.Entry(department).Collection("Employees").Load(); 

        Console.WriteLine(department.DepartmentName);
        Console.WriteLine("------------------");
        foreach (Employee employee in department.Employees)
        {
            Console.WriteLine(employee.FirstName);
        }
        Console.WriteLine("------------------");
    }   
}
Here for each employee/department I am explicitly loading the department of the employee and employees of the department.

Lazy Loading

Ok now let’s move into the third and final way which is the most easiest way among all above. For this you will just have to modify your Navigation properties with Virtual key word.
public class Department
{
    public int DepartmentId { get; set; }
    public string DepartmentName { get; set; }
    public virtual ICollection<Employee> Employees { get; set; }
 
    public static List<Department> GetDepartments()
    {
        return new List<Department>()
        {
            new Department()
            {
                DepartmentId=1,
                DepartmentName="Microsoft Visual Studio"
            },
            new Department()
            {
                DepartmentId=2,
                DepartmentName="Microsoft SQL Server"
            }
        };
    }
} 

public class Employee
{
    public int EmployeeId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public virtual Department Department { get; set; }
 
    public static List<Employee> GetEmployees()
    {
        return new List<Employee>()
        {
            new Employee()
            {
                EmployeeId=1,
                FirstName="Jaliya",
                LastName="Udagedara",
                Department=Department.GetDepartments().First(d=>d.DepartmentId==1)
            },
            new Employee()
            {
                EmployeeId=2,
                FirstName="John",
                LastName="Smith",
                Department=Department.GetDepartments().First(d=>d.DepartmentId==2)
            },
            new Employee()
            {
                EmployeeId=3,
                FirstName="Gary",
                LastName="Smith",
                Department=Department.GetDepartments().First(d=>d.DepartmentId==1)
            }
        };
    }
}
Once you do that, you can use our initial way to query the related entities and you will not see any errors.
using (MyContext context = new MyContext())
{
    foreach (Employee employee in context.Employees)
    {
        Console.WriteLine(employee.FirstName + "-"
            + employee.Department.DepartmentName);
    }
}
Since this can increase performance issues, if you don’t want to enable lazy loading, you can disable lazy loading anytime for your context by specifying following command.
context.Configuration.LazyLoadingEnabled = false;

I am uploading the full sample to my SkyDrive, do check it out.


Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment