How to asynchronously call a relation when lazy loading with Entity Framework 6.4?

Viewed 546

I have a project that used Entity-Framework 6.4 to access data from the database.

I have the following model

public class Product
{
    public int Id { get; set; }
    public string Title { get; set; }
    public bool Available { get; set; }
    // ...
    public int CategoryId { get; set; }
    public virtual Category Category { get; set; }
}

In the controller, I want to access the Category using lazy loading. I know I can use Include() extension to eager load the relation. But instead, I want to lazy load it. Here is an example of how I would like to use it

public async Task<ActionResult> Get(ShowProduct vm)
{
    if (ModelState.IsValid)
    {
        using (var db = new DbContext())
        {
            Product model = await db.Products.FirstAsync(vm.Id);

            vm.Title = model.Title;

            if (model.Avilable)
            {
                // How can call the **Category** property using await operator?
                vm.CategoryTitle = model.Category.Title;
            }
        }
    }

    return View(vm);
}

How can I lazy load the Category relation using await operator to prevent locking the current thread?

2 Answers

How can I lazy load the Category relation using await operator to prevent locking the current thread?

You must explicitly load the reference if you want async.

Etither

await db.Entry(model).Reference<Category>().LoadAsync();

Or, fetch the related entity explicitly, and let the Change Tracker fix-up the navigation property.

var category = await db.Categories.FindAsync(model.CategoryId);
vm.CategoryTitle = model.Category.Title;

I don't know what FirstAsync(vm.Id) does. As far as I know, there is no overload that takes an integer as parameter. I guess your FirstAsync does something similar as:

Product model = await db.Products
    .Where(product => product.Id == vm.Id)
    .FirstAsync();

Or maybe even better: FirstOrDefaultAsync.

Your goal is to do a left outer join, and sometimes not, depending on one of the properties in the left item.

Your solution fetches the left item of the join, extracts the join key, and fetches the right item of the join. In fact, your process is performing a join while executing two database queries.

Database management systems are extremely optimized for joins. Two separate database queries without a join is almost certainly less efficient than one query with a join.

My advice would be to make one query to fetch the data that you would possibly need.

var fetchedData = dbContext.Products
    .Where(product => product.Id == vm.Id)
    .Select(product => new
    {
        Title = product.Title,
        Available = product.Available,
        CategoryTitle = product.Category.Title,
    })
    .FirstOrDefaultAsync();

Now you can fill vm:

if (fetchedData != null)
{
    vm.Title = fetchedData.Title;
    if (fetchedData.Available)
    {
        vm.CategoryTitle = fetchedData.CategoryTitle;
    }
}
// TODO: decide what to do if there is no fetched data?
Related