简体   繁体   中英

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).

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. Eg 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));

I would create a method to fetch the data which takes the properties to include as expressions:

static ArticleContent GetArticleContent(int ID, 
   params Expression<Func<ArticleContent, object>>[] includes)
{
   using(var ctx = new MyContext())
   {
      var acQuery =  _ctx.ArticleContents.Include(a=> a.Metadata);
      foreach(var include in includes)
        acQuery = acQuery.Include(include);
      return acQuery.Single(a => a.Id == model.BaseID);
   }
}

And then in main method:

var ac = GetArticleContent(3, a => a.HeaderImage, a => a.PromoImage);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM