cas4 cas4 - 2 months ago 13
C# Question

Why are navigation properties reset after calling dbContext.Save in EF6

Background: I'm using EF6 and Database First.

I'm run into a scenario that has me perplexed. After creating a new object, populating the Navigation Properties with new objects, and calling SaveChanges, the navigation properties are reset. The first line of code that references the navigation property after the SaveChanges call will end up re-fetching the data from the database. Is this expected behavior, and can someone explain why it behaves this way? Here is a sample code block of my scenario:

using (DbContext context = new DbContext) {
Foo foo = context.Foos.Create();
context.Foos.Add(foo);
...
Bar bar = context.Bars.Create();
context.Bars.Add(bar);
...
FooBar foobar = context.FooBars.Create();
context.FooBars.Add(foobar)
foobar.Foo = foo;
foobar.Bar = bar;

//foo.FooBars is already populated, so 1 is returned and no database query is executed.
int count = foo.FooBars.Count;

context.SaveChanges();

//This causes a new query against the database - Why?
count = foo.FooBars.Count;
}

Answer

I can't say 100%, but I doubt this behavior is made specifically.

As for why it behaves this way, the root of the issue is that DbCollectionEntry.IsLoaded property is false until the navigation property is either implicitly lazy loaded or explicitly loaded using Load method.

Also seems that lazy loading is suppressed while the entity is in Added state, that's why the first call does not trigger reload. But once you call SaveChanges, the entity state becomes Unmodified, and although the collection is not really set to `null' or cleared, the next attempt to access the collection property will trigger lazy reload.

// ...

Console.WriteLine(context.Entry(foo).Collection(e => e.FooBars).IsLoaded); // false
//foo.FooBars is already populated, so 1 is returned and no database query is executed.
int count = foo.FooBars.Count;

// Cache the collection property into a variable
var foo_FooBars = foo.FooBars;

context.SaveChanges();

Console.WriteLine(context.Entry(foo).State); // Unchanged!
Console.WriteLine(context.Entry(foo).Collection(e => e.FooBars).IsLoaded); // false

// The collection is still there, this does not trigger database query
count = foo_FooBars.Count;
// This causes a new query against the database
count = foo.FooBars.Count;

If you want to avoid the reload, the workaround is to explicitly set IsLoaded property to true.

// ...

context.SaveChanges();

context.Entry(foo).Collection(e => e.FooBars).IsLoaded = true;
context.Entry(bar).Collection(e => e.FooBars).IsLoaded = true;
// No new query against the database :)
count = foo.FooBars.Count;