Winetradr
Winetradr

Reputation: 141

EntityFramework lazy loading not working in different context

I'm trying to understand how lazy loading works in Entity Framework 6. It seems to me that lazy loading only works in a DbContext where the "to-be-loaded" property has already been accessed. Please have a look at the example below - the test LazyLoad_InSameContext succeeds, while the test LazyLoad_InDifferentContext fails.

Is this intended behaviour? Shouldn't both tests succeed?

using System.Transactions;
using NUnit.Framework;
using System.Data.Entity;

namespace EfTestProject
{
    [TestFixture]
    public class Test
    {
        [Test]
        public void LazyLoad_InDifferentContext()
        {
            Team team;
            Employee employee;
            using (var context1 = new Context())
            {
                team = new Team();
                context1.Teams.Add(team);
                context1.SaveChanges();
            }
            using (var context2 = new Context())
            {
                employee = new Employee {TeamId = team.Id};
                context2.Employees.Add(employee);
                context2.SaveChanges();

                Assert.NotNull(employee.Team); // <- This fails!
            }
        }

        [Test]
        public void LazyLoad_InSameContext()
        {
            Team team;
            Employee employee;
            using (var context1 = new Context())
            {
                team = new Team();
                context1.Teams.Add(team);
                context1.SaveChanges();

                employee = new Employee { TeamId = team.Id };
                context1.Employees.Add(employee);
                context1.SaveChanges();

                Assert.NotNull(employee.Team); // <- This works!
            }
        }
        [SetUp]
        public void SetUp()
        {
            new Context().Database.CreateIfNotExists();
        }
    }

    public class Context : DbContext
    {
        public Context() : base("name=Context") { }
        public virtual DbSet<Team> Teams { get; set; }
        public virtual DbSet<Employee> Employees { get; set; }
    }

    public class Team
    {
        public int Id { get; set; }
    }

    public class Employee
    {
        public int Id { get; set; }
        public int? TeamId { get; set; }
        public virtual Team Team { get; set; }
    }
}

Edit1:

The test can be "fixed" by adding context2.Teams.Attach(team); before creating the employee. I don't understand why this should be necessary though.

using (var context2 = new Context())
{
    context2.Teams.Attach(team);
    employee = new Employee {TeamId = team.Id};
    context2.Employees.Add(employee);
    context2.SaveChanges();
    employee = context2.Employees.Find(employee.Id);

    Assert.NotNull(employee.Team);
}

Edit2:

It also succeeds, if I create the new entity using context2.Employes.Create():

using (var context2 = new Context())
{
    employee = context2.Employees.Create();
    employee.TeamId = team.Id;
    context2.Employees.Add(employee);
    context2.SaveChanges();

    Assert.NotNull(employee.Team);
}

Upvotes: 3

Views: 1317

Answers (1)

Steve Py
Steve Py

Reputation: 34653

The EF DbContext will not load anything from the database automatically, even with lazy loading enabled. When you associate an entity with a DbContext by Adding it, or attaching it, the DbContext will go through the virtual navigation properties to associate any references it knows about.

For example: Given a Parent/Child relationship where 1 parent has many children and there is a Parent record in the database already with a ParentID of 10.

using (var context = new TestDbContext())
{
  var child = new Child { Name = "test", ParentId = 10 }; 
  context.Children.Add(child); 
  context.SaveChanges();
  Assert.IsNull(child.Parent);
}

Parent ID will be set to 10, but the Parent reference will be null. The Context simply doesn't know about it. If you later load that parent, not even associating it to the entity, presto, the proxy will return it.. The DbContext goes through the loaded entities for anything that might be referencing the loaded parent and associates it. (Behaviour like this is likely why long-lived DbContexts get slow.)

using (var context = new TestDbContext())
{
  var child = new Child { Name = "test", ParentId = 10 };
  context.Children.Add(child);
  context.SaveChanges();
  Assert.IsNull(child.Parent);
  var parent = context.Parents.Find(10);
  Assert.IsNotNull(child.Parent); // Poof, Parent is now a proxy pointing to the Parent.
}

This can get awkward where DbContexts are relatively long-lived as it's knowledge of related entities may vary depending on what may, or may not have been loaded by related code, leading to intermittent NullReferenceExceptions and the like.

This is one good reason to avoid using FK properties in entities that have navigation properties, instead just to use the navigation property with a key mapped behind the scenes or a shadow property. (EF Core) When you use FKs and navigation properties you need to be aware of some potentially weird looking behaviour when getting information from navigation references after setting or changing FKs. It's generally safer to just deal with the navigation properties.

Upvotes: 2

Related Questions