Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specify eager loading with DBContext Find method

How do I tell the Find method of a DBContext that it should eagerly load the navigation property/entity?

I have the following code which deletes the association to a related Secondary entity:

Person primary = db.People.Find(Id);
if (primary == null)
  return new HttpStatusCodeResult(HttpStatusCode.BadRequest);

// This line is required to load the related entity
db.Entry(primary).Reference("Secondary").Load();

primary.Secondary = null;
db.SaveChanges();

I had to add the line db.Entry(primary).Reference("Secondary").Load(); to get it working. I understand this is because the entity framework is using lazy loading. Can I override this in the Find method though and get rid of the extra line by using an Eager version of the Find method?

like image 947
Caltor Avatar asked Aug 22 '16 21:08

Caltor


3 Answers

Eager loading is done by the Include method:

db.People.Include(p => p.Secondary)

Which can be followed by FirstOrDefault(p => p.Id == id), for example.

Find is a DbSet method that first tries to find the requested entity in the context's cache. Only when it's not found there, the entity is fetched from the database.

Because of this special behavior (of Find), Include and Find can't be mixed. It would be hard to define what to do when the root entity is found in the cache, but the entities to be included aren't (or only partly). Should they be fetched from the database? That would imply that Find + Include would always have to query the database for the included entities, because it can't rely on the local cache to be complete. That would defeat the purpose of Find. Or should find only include entities from the local cache if the root entity is in the local cache? That would make the result of the method ambiguous.

In ASP.Net MVC (or Web API) action methods, Find will hardly ever be useful, because most of the times, a new context will be created and entities will be fetched from the database once. In other words, there's nothing in the cache to be returned. You may want to use the method for its succinctness, but the effect, regarding database roundtrips, is the same as FirstOrDefault().

like image 166
Gert Arnold Avatar answered Oct 19 '22 23:10

Gert Arnold


To answer your original question about how to use the Find method with explicit loading (from here)?

using (var context = new BloggingContext())
{
    var post = context.Posts.Find(2);

    // Load the blog related to a given post.
    context.Entry(post).Reference(p => p.Blog).Load();

    // Load the blog related to a given post using a string.
    context.Entry(post).Reference("Blog").Load();

    var blog = context.Blogs.Find(1);

    // Load the posts related to a given blog.
    context.Entry(blog).Collection(p => p.Posts).Load();

    // Load the posts related to a given blog
    // using a string to specify the relationship.
    context.Entry(blog).Collection("Posts").Load();
}

To add to @GertArnolds post, if you use dependency injection where you load a DbContext once per Scoped Lifestyle, which essentially is: (from here):

Only one instance will be created by the container per web request. Use this lifestyle in ASP.NET Web Forms and ASP.NET MVC applications.

Then the DbContext sticks around for a while.

like image 45
Zachary Scott Avatar answered Oct 19 '22 23:10

Zachary Scott


Or you can use

var result = db.Person.Include(c=>c.Secondary).FirstOrDefault(entity=>entity.Id == Id);

Use using System.Data.Entity; for the Linq capability in the Include, otherwise you can just use the string "Secondary" like so: .Include("Secondary")

like image 36
Wurd Avatar answered Oct 19 '22 21:10

Wurd