Virtual keyword, enable extension method, lazy loading, load loading - how related objects actually load

Loading a linked object in MVC can be quite confusing.

There are many terms that you need to know about and find out if you really want to know what you do when you write model classes and entity controllers.

A few questions that I have had for a very long time: How does the virtual keyword work and when should I use it? And how does the Include extension method work and when should I use it?

That is what I am talking about;

virtual keyword:

 using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace LazyLoading.Models { public class Brand { public int BrandId { get; set; } public string Name { get; set; } public int ManufacturerId { get; set; } public virtual Manufacturer Manufacturer { get; set; } } } 

And Include Extension Method:

 using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Web; using System.Web.Mvc; using LazyLoading.Models; namespace LazyLoading.Controllers { public class LazyLoadingStoreController : Controller { private UsersContext db = new UsersContext(); // // GET: /LazyLoadingStore/ public ActionResult Index() { var brands = db.Brands.Include(b => b.Manufacturer); return View(brands.ToList()); } ... Other action methods ... 

Note that the Index() action method is automatically generated by Visual Studio. Yes, Visual Studio automatically added .Include(b => b.Manufacturer) . It is very nice.

+6
source share
3 answers

I have created a test MVC4 internet application.

Here is what I found:

First, create object model classes - pay attention to the virtual for the Manufacturer property:

 public class Manufacturer { public int ManufacturerId { get; set; } public string Name { get; set; } public ICollection<Brand> Brands { get; set; } } public class Brand { public int BrandId { get; set; } public string Name { get; set; } public int ManufacturerId { get; set; } public virtual Manufacturer Manufacturer { get; set; } } 

Then create your controller - I created (autogenerated by creating a new controller dialog box) with my CRUD methods and views. Pay attention to the Include extension method, which is automatically generated automatically by Visual Studio due to the correlation in the class of the Brand model.

 public class LazyLoadingStoreController : Controller { private UsersContext db = new UsersContext(); // // GET: /LazyLoadingStore/ public ActionResult Index() { var brands = db.Brands.Include(b => b.Manufacturer); return View(brands.ToList()); } 

Now remove the Include part so that our action method looks like this:

 public ActionResult Index() { var brands = db.Brands; return View(brands.ToList()); } 

And here is how the Index view will look in the Page Inspector after adding a pair of Brand objects - note that Visual Studio automatically adds a drop-down menu for Manufacturer and how it automatically aligns the Name column for Manufacturer - sweet !: enter image description hereenter image description here

Create action method:

 // // GET: /LazyLoadingStore/Create public ActionResult Create() { ViewBag.ManufacturerId = new SelectList(db.Manufacturers, "ManufacturerId", "Name"); return View(); } 

Tall. Everything was auto-generated for us!

Now, what happens if we remove the virtual from our Manufacturer property?

 public class Brand { public int BrandId { get; set; } public string Name { get; set; } public int ManufacturerId { get; set; } public Manufacturer Manufacturer { get; set; } } 

This is what will happen - our manufacturer details are gone:

enter image description here

Ok, that makes sense. What if I add an Include extension method (with virtual still removed from the Manufacturer property)?

 public ActionResult Index() { var brands = db.Brands.Include(b => b.Manufacturer); return View(brands.ToList()); } 

This is the result of adding the Include extension method - Manufacturer data is back !:

enter image description here

So how does it all work.

The next step would be to explain which T-SQL is generated behind the scenes in both cases (Lazy loading and Eager loading). What will I leave to someone else. :)

Note. Visual Studio automatically generates Include(b => b.Manufacturer) if you add the virtual or not.

Note2: Oh yes. Almost forgot. Here are some links to some good Microsoft resources.

The second link talks about performance considerations that the other link is missing if that is what you need.

+6
source

Note. It took me too much time to write this answer, just to refuse it when the other two appeared ...

The virtual keyword works along with two DbContext.Configuration properties:

  • ProxyCreationEnabled - Allows EF to break a dynamic proxy when an object is created by EF
  • LazyLoadingEnabled - allows a dynamic proxy to load related objects when the navigation property is used for the first time

Lazy loading is transparently implemented through a dynamic proxy. A dynamic proxy is a class derived from your object that is created and compiled by EF at runtime. It overrides your virtual navigation properties and implements a logical check if related objects are already loaded or not. If this does not cause the context to load (it issues a new request to the database). Lazy loading can only be performed in the context of the context to which the entity is attached - if you manage the context, you cannot use it.

The creation of a dynamic proxy is controlled by the first property mentioned. When an instance of an object is created as proxied, you cannot “delete” the proxy. In addition, if you create an entity without a proxy (for example, by calling the constructor yourself), you cannot add it later (but you can use DbSet<T>.Create instead of the constructor to get a proxied instance).

The second property can be changed in real time of your entity instances, so you can avoid unnecessary database queries when working with your objects by changing it to false (sometimes this is very useful).

Include is a download. A bright load loads the related objects along with the main object and runs as part of the query of the main object (it adds SQL queries to the query and builds large result sets).

The advantage of an active download is to get all the data in advance with one call to the database. Especially if you know that you will need all of them, this may be the way to go. The disadvantage of impatient loading is the very large result sets if you use too many inclusions, as well as some restrictions (you cannot add ordering or filtering for loaded objects - it always loads all related objects).

The benefits of lazy loading are that you only download data when you really need it. This is useful if you do not know in advance if you really need to. The drawbacks are additional requests generated by EF in some scenarios when you do not expect them (any first access to this property will lead to lazy loading - even Count in the navigation collection will load all the data that can count into your application instead of asking for a counter from databases - this is called extra lazy loading , and it is not yet supported by EF). Another big problem is the N + 1 problem. If you upload several brands and you get access to the manufacturer’s property, going through all the downloaded brands, without using intensive loading, you will generate N + 1 database queries (where N is the number of brands ) - one for downloading all brands and one for the manufacturer of each brand.

There is another option called explicit loading. This seems like lazy loading, but for you it is not transparent. You must execute it yourself using the context class:

 context.Entry(brand).Reference(b => b.Manufacturer).Load(); 

In this case, this is not very useful, but it would be useful if you have the Brands navigation property in the Manufacturer class, because you can do this:

 var dataQuery = context.Entry(manufacturer).Collection(m => m.Brands).Query(); 

Now you have an instance of IQueryable<Brand> , and you can add any condition, arrange, or even add an extra load and execute it against the database.

+11
source

lazy loading

Brand is POCO (plain old CLR object). This persistence is ignorant. In other words: he does not know that he was created by the Entity Framework data layer. He knows even less how to download his Manufacturer .

However, when you do this

 var brand = db.Brands.Find(1); var manufacturer = brand.Manufacturer; 

Manufacturer loads on the fly (lazy). If you track the SQL sent to the database, you will see that the second query is emitted to get the Manufacturer .

This is because under the hood, EF does not create an instance of Brand , but a derived type, a proxy that is filled with wiring to perform lazy loading. This is why the virtual modifier is needed to provide lazy loading: the proxy server must be able to override it.

Lazy loading is typically used in smart client applications where the context has a relatively long life (for example, context for each form). Although I must say that even in smart client applications that use short-lived contexts, it is useful and completely possible.

Unwanted loading

The desired download means that you download the object with attached objects (parents and / or children) at a time. This is the Include method. In the example

 db.Brands.Include(b => b.Manufacturer) 

You will see that EF creates a SQL query with a join and that access to the Brand Manufacturer no longer generates a separate query.

Desired loading is the way to go in most situations (especially in disconnected scenarios), because the recommended way to handle context instances is to use them and manage them for each unit of work. Thus, lazy loading is not an option, because in order for the navigation property to be lazy, the context must be alive.

+1
source

Source: https://habr.com/ru/post/944568/


All Articles