Lazy loading when using Single and SingleOrDefaultAsync - entity-framework

I have two data models
ApplicationUser and Team
public class Team : OwnerEntity
{
public virtual ICollection<ApplicationUser> Members { get; set; } = new HashSet<ApplicationUser>();
public int MaxTeamSize { get; set; }
public string TeamName { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public string AvatarUri { get; set; }
public Team(string teamName, int teamSize)
{
TeamName = teamName;
MaxTeamSize = teamSize;
}
public Team()
{
}
}
public class ApplicationUser : IdentityUser
{
public virtual ICollection<Team> Teams { get; set; } = new HashSet<Team>();
[ForeignKey("ActiveTeamId")]
public string ActiveTeamId { get; set; }
public virtual Team ActiveTeam { get; set; }
}
I noticed behaviour that I cannot understand when I use
var teams = db.Teams.Single();
I get the team without Members, and that is correct. However when I use
await db.Teams.SingleOrDefaultAsync(_ => _.Id == UserTeamId);
It always returns Members event though I did not include it anywhere with
db.Teams.Inlclude(_ => _.Members)
Also calling one after another
public async Task<IActionResult> TEst()
{
var teams = db.Teams.Single();
var querySingle = await db.Teams.SingleOrDefaultAsync(_ => _.Id == UserTeamId);
return new JsonResult(querySingle);
}
Results in teams.Single() also returns a list of members. I think I do not fully understand LazyLoading mechanism. Can someone explain this case?

With EF Core you need to enable lazy loading proxies before lazy loading will function. The easiest way to observe lazy load calls is with a profiler running against a test DB that is not being accessed by anything but your code under test.
If lazy loading is not enabled, EF's behaviour will associate any tracked entities it has references to. For example, given a Parent (ID 1) with 3 Children (IDs 1, 2, & 3) If at some point we load the children:
var children = context.Children.Where(x => x.ParentId == 1);
... then later load the parent:
var parent = context.Parents.Single(x => x.ParentId == 1);
var count = parent.Children.Count; // returns 3.
... you can access parent.Children and you will receive the 3 children even though you did not eager load them. The DbContext was tracking the references so it populated them. This can be rather dangerous situationally because take the following example:
var child = context.Children.Single(x => x.ChildId == 3);
var parent = context.Parents.Single(x => x.ParentId == 1);
var count = parent.Children.Count; // returns 1.
parent.Children will be populated here, but will only contain Child #3 if that is the only child the DbContext was tracking.
I would highly suspect that lazy loading is not actually enabled in your project. To test whether lazy loading is actually working, I would instantiate a new DbContext with the following check, watching a profiler for SQL hits:
using (var context = new AppDbContext()) // Your app context here.
{
var parent = context.Parents.Single(x => x.ParentId == 1); // load a single parent.
var count = parent.Children.Count; // <- breakpoint on this line.
}
Hit the breakpoint and watch the profiler. After you step over the line, did an SQL query get executed? Note even if you hover over Children in the debugger, this would trigger the lazy load proxy in most cases. (or generate a warning that threads need to execute) If count comes back with the # of children then that confirms lazy loading is configured properly. If it comes back as "0" then lazy loading isn't enabled.
For EF Core you need to include the EntityFrameworkCore.Proxies NuGet package and initialize the optionsBuilder in the DbContext .OnConfiguring handler to enable lazy loading proxies:
optionsBuilder.UseLazyLoadingProxies();
Then the tracked references for the Children collection are ignored and a lazy load call will be made.
var child = context.Children.Single(x => x.ChildId == 3);
var parent = context.Parents.Single(x => x.ParentId == 1);
var count = parent.Children.Count; // returns 3. Lazy load SQL call observed.
I tried this out with both Sync and Async calls and there was no observed difference. I suspect the behaviour you were seeing was that lazy loading isn't actually enabled and you are making an async call sometime after the DbContext has already loaded the associated children. That a call to the DbContext can return children without eager loading sometimes and not others can be a rather confusing and bug-prone behaviour. If lazy loading is configured correctly and active it should return more reliable results but I treat it more as a failsafe rather than designing for it. It's generally always better to leverage eager loading and projection to load data rather than relying on lazy loading.

Related

Entity Framework Navigation Property preload/reuse

Why is Entity Framework executing queries when I expect objects can be grabbed from EF cache?
With these simple model classes:
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Content { get; set; }
public virtual Blog Blog { get; set; }
}
public class BlogDbContext : DbContext
{
public BlogDbContext() : base("BlogDbContext") {}
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
I profile the queries of following action
public class HomeController : Controller
{
public ActionResult Index()
{
var ctx = new BlogDbContext();
// expecting posts are retrieved and cached by EF
var posts = ctx.Posts.ToList();
var blogs = ctx.Blogs.ToList();
var wholeContent = "";
foreach (var blog in blogs)
foreach (var post in blog.Posts) // <- query is executed
wholeContent += post.Content;
return Content(wholeContent);
}
}
Why doesn't EF re-use the Post entities which I had already grabbed with the var posts = ctx.Posts.ToList(); statement?
Further explanation:
An existing application has an Excel export report. The data is grabbed via a main Linq2Sql query with a tree of includes (~20). Then it is mapped via automapper and additional data from manual caches (which previously slowed down the execution if added to the main query) is added.
Now the data is grown and SQL Server crashes when trying to execute the query with an error:
The query processor ran out of internal resources and could not produce a query plan.
Lazy loading would result in >100.000 queries. So I thought I could preload all the required data with a few simple queries and let EF use the objects automatically from cache during lazy loading.
There I initial had additional problems with limits of the TSQL IN() clause which I solved with MoreLinq´s Batch extension.
When you have Lazy Loading enabled, EF will still reload the Collection Navigation Properties. Probably because EF doesn't know whether you have really loaded all the Posts. EG code like
var post = db.Posts.First();
var relatedPosts = post.Blog.Posts.ToList();
Would be tricky, as the Blog would have one Post already loaded, but obviously the others need to be fetched.
In any case when relying on the Change Tracker to fix-up your Navigation Properties, you should disable Lazy Loading anyway. EG
using (var db = new BlogDbContext())
{
db.Configuration.LazyLoadingEnabled = false;
. . .
Given you have the navigation properties, look at leveraging them in your query to feed Automapper a dynamic object to map to your ViewModel/DTO rather than a top-level entity which you'd be relying on eager loading or waiting on lazy loading.
This is done by issuing a .Select() on your query. To use a simple example of extracting order details including the customer name, list of product names and quantities from order lines, and the delivery address where an Order has a reference to customer, and that customer has a delivery address, a collection of order lines, each with a product...
var orderDetails = dbContext.Orders
.Where(o => /* Insert criteria */)
.Select(o => new
{
o.OrderId,
o.OrderNumber,
o.Customer.CustomerId,
CustomerName = x.Customer.FullName,
o.Customer.DeliveryAddress, // Address entity if no further dependencies, or extract fields/relations from the Address.
o.OrderLines.Select( ol = > new
{
ol.OrderLineId,
ProductName = ol.Product.Name,
ol.Quantity
}
}).ToList(); // Ready to feed into Automapper.
With ~20 includes your Select will undoubtedly be a bit more involved, but the idea is to feed SQL Server a query to retrieve just the data you want that you can then feed into Automapper to navigate through where any child relationships can either be flattened by EF or simplified and returned for your mapper to flesh out into the resulting models.
With growing systems you will also want to consider leveraging paging /w Skip and Take rather than ToList, or at least leveraging Take to ensure that there is a cap to the amount of data your return. ToList is a primary performance troll that I look for in EF code because its misuse can kill applications.

Linq Entry( entity.Related ).Query.Load() not writing back to Collection property

Supposing I have three EF entity classes:
public class Person {
...
public ICollection Vehicles { get; set; }
}
public class Vehicle {
...
public Person Owner { get; set; }
public CarModel ModelInfo { get; set; }
}
public class CarModel {
...
// properties for make, model, color, etc
}
The Person.Vehicles property is lazy-loaded.
Supposing I have a Person instance already loaded and I want to load its Vehicle collection property such that it also includes the related ModelInfo property.
So I have this:
void LoadVehiclesAndRelated(MyDbContext dbContext, Person person)
{
dbContext.Entry( person )
.Collection( p => p.Vehicles )
.Query()
.Include( v => v.ModelInfo )
.Load();
}
Used like so:
using( MyDbContext dbContext = ... ) {
Person p = GetPerson( 123 );
LoadVehiclesAndRelated( dbContext, p );
}
foreach(Vehicle v in p.Vehicles) {
Console.WriteLine( v.ModelInfo );
}
However when I do this, I get an exception at runtime when it first evaluates the p.Vehicles expression because the property is actually empty (so it wants to load it) but the DbContext is now disposed.
When the .Load() call was made (inside LoadVehiclesAndRelated() I saw the SQL being executed against the server (in SQL Server Profiler) but the collection property remains empty.
How can I then load the property and with the Included sub-properties?
Annoyingly, this scenario is not mentioned in the MSDN guide for explicit-loading: https://msdn.microsoft.com/en-us/data/jj574232.aspx
Looks like calling .Query().Load() is not the same as calling DbCollectionEntry.Load directly, and the important difference is that the former does not set the IsLoaded property, which then is causing triggering lazy load later. Most likely because as explained in the link, the former is intended to be used for filtered (partial) collection load scenarios.
Shortly, to fix the issue, just set IsLoaded to true after loading the collection:
var entry = dbContext.Entry(person);
var vehicleCollection = entry.Collection(p => p.Vehicles);
vehicleCollection.Query()
.Include( v => v.ModelInfo )
.Load();
vehicleCollection.IsLoaded = true;
P.S. For the sake of correctness, this behavior is sort of mentioned at the end of the Applying filters when explicitly loading related entities section:
When using the Query method it is usually best to turn off lazy loading for the navigation property. This is because otherwise the entire collection may get loaded automatically by the lazy loading mechanism either before or after the filtered query has been executed.

EF Lazy loading - can I retrieve child collection later?

I use Code First with Entity Framework.
I have a class with virtual property to another class (lazy loading).
public class Order{
public int Id { get; set; }
public virtual ICollection<OrderItem> OrderItems { get; set; }
}
If I get Order from database and do not include OrderItem, then close DbContext, is it possible to load them later? If yes, how?
eg.
private static Order GetFirstOrder(Func<Order, bool> predicate)
{
using (var db = new MyContext())
{
return db.Orders.First(predicate);
}
}
private static void DoSomething()
{
var order = GetFirstOrder(a => a.Id == 1);
//do something with OrderItems later?
}
Lazy loading will be available as long as the context of the query is alive.
If it's closed, then it's over, you'll have to re-query (some GetOrderItemsByOrder query), or re-attach. Do something "manually", in any case.
You should Include the collection name.
var myItemWithCollection = (from s in db.tableName.Include("ListName").Where(s => s.Id.Equals(ItemId)) select s).FirstOrDefault();

Enumeration Succeeds after DbContext Disposed

Background
I am creating a projection from a parent/child relationship that includes a Name property of the parent and a list of the children's Ids.
Code
private class ParentChildInfo
{
public string Name { get; set; }
public List<int> ChildIds { get; set; }
}
var infos = ctx.Masters.Include(m => m.Children).Select(
m => new ParentChildInfo()
{
Name = m.Name,
ChildIds = m.Children.Where(c => c.SomeProp.StartsWith("SpecialValue"))
.Select(c => c.Id).ToList()
}).ToList();
Unfortunately that produced the error
LINQ to Entities does not recognize the method 'System.Collections.Generic.List`1[System.Int32] ToList[Int32]
That lead me to this post, which suggested (in the comments) making the following changes:
private class ParentChildInfo
{
public string Name { get; set; }
public IEnumerable<int> ChildIds { get; set; } // No longer List<int>
}
var infos = ctx.Masters.Include(m => m.Children).Select(
m => new ParentChildInfo()
{
Name = m.Name,
ChildIds = m.Children.Where(c => c.SomeProp.StartsWith("SpecialValue"))
.Select(c => c.Id) // Dropped the .ToList()
}).ToList();
I originally wanted to get lists rather than enumerables because the code that uses the result runs for several minutes, and I did not want to tie up the DbContext that long.
I use the code like this:
using (MyContext ctx = new MyContext())
{
// code from above that populates infoes
}
foreach (var info in infoes)
{
// use info.ChildIds
}
I planned to move the foreach into the using so that I can enumerate the ChildIds, but hit F5 instead and was surprised to see that the code works.
Question
Given that the DbContext is disposed at that point and ChildIds is an IEnumerable<int> rather than List<int>, why exactly can I enumerate ChildIds?
It is because the ToList() of the infos query actually executes the query. So the collection ctx.Masters is enumerated and the projections are populated. Even without the Include it would notice that Master.Children is addressed and emit the SQL join. The implementing type of IEnumerable<int> ChildIds is probably List<int>.
You did .ToList() on the query so the query was executed and all the results are materialized and the connection to the database should be closed. I assume it would not work if you did not have .ToList() since (at least in EF5) the results are being processed in streaming fashion and entities are materialized when requested (i.e. on each iteration in the loop).

Ramifications of DbSet.Create versus new Entity()

I am a bit confused about whether to use DbSet.Create, or simply new up an entity and add it. I don't really understand the ramifications of using DbSet.Create.
I understand that DbSet.Create will create a proxied version if applicable, but I don't really understand what that means. Why do I care? It seems to me that an empty Proxied class is no more useful than a non-proxied class, since there are no related entities to lazy load.
Can you tell me the difference, beyond the obvious? And why would you care?
A scenario where using DbSet<T>.Create() makes sense is attaching an existing entity to the context and then leverage lazy loading of related entities. Example:
public class Parent
{
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
}
The following would work then:
using (var context = new MyDbContext())
{
var parent = context.Parents.Create();
parent.Id = 1; // assuming it exists in the DB
context.Parents.Attach(parent);
foreach (var child in parent.Children)
{
var name = child.Name;
// ...
}
}
Here lazy loading of children is triggered (perhaps with resulting empty collection, but not null). If you'd replace context.Parents.Create() by new Parent() the foreach loop will crash because parent.Children is always null.
Edit
Another example was here (populating a foreign key property of a new entity and then getting the navigation property lazily loaded after the new entity is inserted into the DB): Lazy loading properties after an insert