Consider the following entity model and function:
public class Order
{
public int OrderId {get; set;}
public int StatusId {get; set;}
public virtual Status OrderStatus {get; set;}
}
public class Status
{
public int StatusId {get; set;}
public String Name { get; set;}
}
public void ShowOrders()
{
//load all status entities.
//Will EF check for these in object cache first when I access order.Status for
//the first time?
//Or perhaps even auto include them in materialised orders?
context.Status.Load();
//enumerate orders without explicit status include
foreach(Order o in context.Orders.ToList())
{
//Get Status navigation property for each order
//Will database be hit?
Console.WriteLine("Order: {0:N}, Status: {1}", o.OrderId, o.OrderStatus.Name);
}
}
I know I can explicitly do:
context.Orders.Include(o=>o.OrderStatus).ToList();
to include Status when querying for orders to prevent n+1 selects. And I know that if I access Order.OrderStatus
navigation property that DbReferenceEntry.IsLoaded
is checked and a cached Status object retrieved if possible before database is hit.
What I'm wondering is if DbReferenceEntry.IsLoaded
and DbReferenceEntry.CurrentValue
are populated when the parent entity is materialised (even if .Include()
is not called) if the reference entity is in the object cache already?
So in this example above when accessing Order.OrderStatus
for the first time does a database query get executed even though all Status are in object cache because of the Status.Load()
call prior to enumerating orders?
EF will check the object cache first whenever a navigation property is accessed, so if all the Status entities are already loaded accessing Order.OrderStatus shouldn't issue a database query. DbReferenceEntry will be populated during materialisation if the reference is already in the object cache.
If you are concerned about the number of queries being issued consider turning off Lazy Loading so in cases where an automatic query would be issued you'll get a null instead.
For more performance guidance see this article: http://msdn.microsoft.com/en-us/data/hh949853.aspx
I didn't test the following statements (by watching when and which SQL queries are actually run in a profiler), they are only guesses:
does a database query get executed even though all Status are in object cache because of the Status.Load() call prior to enumerating orders?
If a database query is not executed I'm pretty sure that the reason is not that specifically context.Status.Load()
is called. This is only a snapshot of the Status
database table at the time when context.Status.Load()
is executed. At the point where you enumerate the orders EF can't be sure that the Status
table hasn't change in the meantime and additional Status
rows have been inserted. So, to avoid a wrong data representation EF must run a new query - unless EF has other means to recognize if a query is required or not.
And there are other means, in this case because an Order
entity has only a reference to Status
, not a Status
collection. When the Order
s get loaded - by enumerating context.Orders.ToList()
- EF will always load the foreign key to OrderStatus
, no matter if you use Include
for the Status
or not. It would even be the case if you hadn't exposed the FK StatusId
as property in your model. At the moment when an Order
is materialized, relationship fixup will run and check if a Status
entity whose primary key has the same value as the FK loaded together with the Order
already exists in the object context. If yes, the Order.OrderStatus
property will immediately be set to that Status
entity - and I guess, EF will mark the navigation property as IsLoaded
. For a reference there can be only one matching entity and it wouldn't make sense to run a query if that entity is already attached to the context and assigned to the navigation property.
So, I would say: No query is run if the right Status
object is attached to the context. It doesn't matter if by running context.Status.Load()
or by loading only this specific Status
, running any other query that would load this Status
or by manually attaching the Status
to the context ( context.Status.Attach(...)
).
This behaviour must change in my opinion if Order.OrderStatus
would be a collection . When an Order
gets loaded now there is no FK to Status
. Instead the Status
had an FK to Order
. If you load Status
entities first - by context.Status.Load
or any other query - the Order-FK would be loaded with the Status
objects. If you later load an Order
relationship fixup would run again, this time the other way around: EF looks if there are any Status
objects in the context that have an FK refering to the loaded Order
. If yes, it will add the Status
to the Order.OrderStatus
collection. But this time - big guess - it can't mark the navigation property (the Order.OrderStatus
collection) as IsLoaded
because it can't be sure that really all Status
objects for this Order
have been loaded previously or that a new Status
for that Order
hasn't been added to the database in the meantime.
So, I guess, if you access the Order.OrderStatus
collection lazy loading will run to ensure that the potential "rest of Status
objects" for that Order
gets loaded as well. Then it will mark the collection as IsLoaded
. It might not need to add any additional Status
to the collection, but the query is necessary at least to check if the collection was complete or not.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.