I'm using Entity Framework 6.1.3 in an MVC 5 project, and I'm having issues with ICollection navigation properties being null when unit testing. I am hitting an actual test database in SQL Express, so this may be more like an integration test for you purists. Regardless of what you call it, it is a problem that I would like to solve.
I have read many answers to similar sounding questions, but none of them seem to hit the same problem that I am having here. I understand EF for the most part, I have lazy loading enabled, my classes are public, and I'm using virtual
on my navigation properties.
Here is a simplified example of what I am trying to do:
public class Session
{
public int Id { get; set; }
public string Name { get; set; }
// Navigation Property
public virtual ICollection<File> Files { get; set; }
}
public class File
{
public int Id { get; set; }
public string Name { get; set; }
public int SessionId { get; set; }
public virtual Session Session { get; set; }
}
[TestMethod]
public void Test_TotalFileCount1()
{
ApplicationDbContext context = new ApplicationDbContext();
// Create session with no files
var session = new Session() { Name = "Session1" };
context.Sessions.Add(session);
context.SaveChanges();
// This line blows up because session.Files == null
Assert.AreEqual(0, session.Files.Count);
}
[TestMethod]
public void Test_TotalFileCount2()
{
ApplicationDbContext context = new ApplicationDbContext();
// Create session
var session = new Session() { Name = "Session2" };
context.Sessions.Add(session);
context.SaveChanges();
// Create file for session
var file = new File() { Name = "File1", Session = session };
context.Files.Add(file)
context.SaveChanges();
// This test passes because session.Files is a
// collection of one file
Assert.AreEqual(1, session.Files.Count);
}
The first test above fails because session.Files
throws an ArgumentNullException
. However, when I call this same code in the full MVC application, session.Files
is not null and instead is an empty collection with Count = 0
. The second test passes because session.Files
is a collection of one File
as I would expect. The navigation properties are clearly doing what they're supposed to in the second case, but not in the first case.
Why is EF behaving like this?
I was able to get around this problem by initializing Files
as an empty list in the constructor. I know I could do this conditionally in the getter instead, but I don't think I should have to do either of these things because it just works when it's running normally.
public Session()
{
this.Files = new List<File>();
}
Does anyone have any insight into what is going on here?
If you are working with lazy loading enabled and if you want that the navigation property gets populated after adding the object with the foreign key property to the context you must use the Create method of DbSet (instead of instantiating the object with new):
var session = context.Sessions.Create();
With active lazy loading this will create a proxy object which ensures that the navigation property gets loaded.
[TestMethod]
public void Test_TotalFileCount1()
{
ApplicationDbContext context = new ApplicationDbContext();
// Create session with no files
var session = context.Sessions.Create();
session.Name = "Session1";
context.Sessions.Add(session);
context.SaveChanges();
// This line blows up because session.Files == null
Assert.AreEqual(0, session.Files.Count);
}
[TestMethod]
public void Test_TotalFileCount2()
{
ApplicationDbContext context = new ApplicationDbContext();
// Create session
var session = context.Sessions.Create();
session.Name = "Session2";
session.Files = new List<File>()
{
new File() { Name = "File1" }
};
context.Sessions.Add(session);
context.SaveChanges();
// This test passes because session.Files is a
// collection of one file
Assert.AreEqual(1, session.Files.Count);
}
See more about Proxies