Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditionally Include and ThenInclude

As commonly known in EF-Core there is no Lazy loading. So that kind of means I'm forced to do my queries with some afterthought. So since I have to think, then i might as well try to do it properly.

I have a Fairly standard update query, but I thought hey, I don't always have to include the HeaderImage and PromoImage FK-objects. There should be a way to make that happen. But I can just not find a way to perform a Include at a later point. In-fact I would like to maybe include right before I actually do work on the object. That way i might be able to automate some of the eagerness.

ArticleContent ac = _ctx.ArticleContents
    .Include(a=> a.Metadata)
    .Include(a=> a.HeaderImage)
    .Include(a=> a.PromoImage)
    .Single(a => a.Id == model.BaseID);

ac.Title = model.Title;
ac.Ingress = model.Ingress;
ac.Body = model.Body;
ac.Footer = model.Footer;

if (model.HeaderImage != null)
{
    ac.HeaderImage.FileURL = await StoreImage(model.HeaderImage, $"Header_{model.Title.Replace(" ", "_")}_{rand.Next()}");
}
if (model.PromoImage != null)
{
    ac.PromoImage.FileURL = await StoreImage(model.PromoImage, $"Promo_{model.Title.Replace(" ", "_")}_{rand.Next()}");
}

ac.Metadata.EditedById = uId;
ac.Metadata.LastChangedTimestamp = DateTime.Now;

await _ctx.SaveChangesAsync();

EXTRA

To be clear, this is EF7 (Core), and im after a solution that allows me to add includes on demand, hopefully after the initial _ctx.ArticleContents.Include(a=> a.Metadata).Single(a => a.Id == model.BaseID).

like image 989
Thomas Andreè Wang Avatar asked Oct 31 '22 05:10

Thomas Andreè Wang


1 Answers

I'm using something similar to Alexander Derck's solution. (Regarding the exception mentioned in the comments: ctx.ArticleContents.AsQueryable() should also work.)

For a couple of CRUD MVC sites I'm using a BaseAdminController. In the derived concrete Controllers I can add Includes dynamically. From the BaseAdminController:

// TModel: e.g. ArticleContent
private List<Expression<Func<TModel, object>>> includeIndexExpressionList = new List<Expression<Func<TModel, object>>>();

protected void AddIncludes(Expression<Func<TModel, object>> includeExpression)
{
    includeIndexExpressionList.Add(includeExpression);
}

Later I saw that I need more flexibility, so I added a queryable. E.g. for ThenInclude().

private Func<IQueryable<TModel>, IQueryable<TModel>>  IndexAdditionalQuery { get; set; }

protected void SetAdditionalQuery(Func<IQueryable<TModel>, IQueryable<TModel>> queryable)
{
    IndexAdditionalQuery = queryable;
}

Here the Index action:

public virtual async Task<IActionResult> Index()
{
    // dynamic include:
    // dbset is for instance ctx.ArticleContents
    var queryable = includeIndexExpressionList
        .Aggregate(dbSet.AsQueryable(), (current, include) => current.Include(include));
    if(IndexAdditionalQuery != null) queryable = IndexAdditionalQuery(queryable);
    var list = await queryable.Take(100).AsNoTracking().ToListAsync();
    var viewModelList = list.Map<IList<TModel>, IList<TViewModel>>();

    return View(viewModelList);
}

In the concrete Controller I use:

AddIncludes(e => e.EventCategory);

SetAdditionalQuery(q => q
    .Include(e => e.Event2Locations)
    .ThenInclude(j => j.Location));
like image 83
noox Avatar answered Nov 15 '22 06:11

noox