Reputation: 363
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;
}
Upvotes: 3
Views: 808
Reputation: 205769
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;
Upvotes: 1
Reputation: 13272
I think the issue is the concept of 'context' is being confused. You are in a 'connected' state when you are in the context of EF. It doesn't always care to reuse your data smartly. It knows "I exist as an extension of the database level through a context set up to talk to it." If you have an object you create or one already existing say 'FooBars' and you do this:
foo.Foobars.(some operation)
If foo is your context and Foobars is some object off of it, it is referenced as an extension of context. If you want to realize reuse without incurring roundtrip to the database collect an object or objects outside the scope of the context like:
var foobars= new Foobars;
using(var foo = new new DbContext)
{
foobars = foo.FooBars;
}
var cnt = foobars.Count();
Generally speaking with EF, I see a lot of people do something like have a whole 'using(var context = new Efcontext())' over an entire method that may be long and do this process over and over everywhere. It is not bad out of the box per say, but doing this everywhere I would simply say: "Do you need to keep a db connection open over and over again?" Sometimes you do, most of the time you do not though.
Upvotes: 0
Reputation: 921
Why would you expect it not to re-query the database? EF isn't holding a complete cached copy of your database. What if you have some trigger(s) doing a row insert after something you changed or some column that serves as a function column that EF doesn't know the exact calculation for? It needs to get the most recent data otherwise your Count
won't be correct.
Upvotes: 0