Parameterizing ThenInclude() in Entity Framework

David Thielen 3,211 Reputation points
2023-06-22T03:42:52.77+00:00

I've created a way to set up my EF queries to take what gets Include() via passing a parameter.

public static class AppUserExtensions
{
    public static IQueryable<AppUser> FindById(this IQueryable<AppUser> source, int userId, Expression<Func<AppUser, object?>>[] includes)
    {
        var query = source.Where(u => u.Id == userId);
        foreach (var include in includes)
            query = query.Include(include);
        return query;
    }
}

public static class AppUserIncludes
{
    public static Expression<Func<AppUser, object?>>[] Organizations => new Expression<Func<AppUser, object?>>[]
    {
        u => u.Campaigns,
        u => u.Cantons,
        u => u.States,
        u => u.Countries,
    };
}

The beauty of this approach is I avoid long queries strewn throughout my code and instead have a set of queries for any kind of search and then this set of arrays to select which properties to Include(). And I can use any array with any query.

But... I'm now facing the same issue for ThenInclude().

What I want is some kind of object to build an array of like (what I'm writing here I know is not valid):

Complex<T1, T2> Expression<Expression<Func<T1, object?>>, Expression<Func<T2, object?>>[]>

I'm struggling with exactly what/how to do this. But I want to be able to create something where I pass:

<AppUser, Campaign>(u => u.Campaigns, CampaignExtensions.Parents)

And then in the query I can do something like:

 foreach (var thenInclude in thenIncludes)
            query = query.Include(include).ThenInclude(thenInclude);

I know I may not be explaining this perfectly as I'm not sure how to accomplish this and that's why I'm asking. I hope this is enough that someone can help. Is there a way to do this?

Also, I assume there's no downside to the eventual query possibly having the same Include(u => u.Campaigns) in ot 3 or 5 times. Doesn't SQL Server reduce this down to the same SQL as it figures out all the expressions?

??? - thanks - dave


Further thought. If I call Include() as follows, it returns a type that knows what type it is returning a collection of:

IIncludableQueryable<Campaign, ICollection<AppUser>> x = query.Include(campaign => campaign.Followers!);

So where T1 in this case is AppUser, it returns an object that knows Campaign needs to be the type for T2. So if there's a way to know that in the code so it is type checking as I write it, and at compile time, that would be optimal.

The information is all there so I'm guessing this is possible. I added the tag for C# because this may be more a generics question and not so much an EF question.

Developer technologies .NET Entity Framework Core
Developer technologies C#
{count} votes

Accepted answer
  1. Karen Payne MVP 35,586 Reputation points Volunteer Moderator
    2023-06-22T11:39:57.6+00:00

    Seems you should be able to do this with path The dot-separated list of related objects to return in the query results.

    Example Include("Addresses.StateProvince.CountryRegion")

    public static System.Linq.IQueryable<T> Include<T> (this System.Linq.IQueryable<T> source, string path);
    
    
    1 person found this answer helpful.
    0 comments No comments

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.