9

EF Core 3.1 I have seen Specification example, and want to implement ThenInclude pattern.

public static class QuerySpecificationExtensions
{
    public static IQueryable<T> Specify<T>(this IQueryable<T> query, ISpecification<T> spec) where T : class
    {
        // fetch a Queryable that includes all expression-based includes
        var queryableResultWithIncludes = spec.Includes
            .Aggregate(query,
                (current, include) => current.Include(include));

        // modify the IQueryable to include any string-based include statements
        var secondaryResult = spec.IncludeStrings
            .Aggregate(queryableResultWithIncludes,
                (current, include) => current.Include(include));

        // return the result of the query using the specification's criteria expression
        return secondaryResult.Where(spec.Criteria);
    }
}

I can add this into string for example "User.UserRole.Role", but I want to implement object. Maybe there it is not possible?

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
hdoitc
  • 386
  • 1
  • 10
  • 21

1 Answers1

19

Includes member of the aforementioned ISpecification<T> is declared as

List<Expression<Func<T, object>>> Includes { get; }

The problem is that EF Core Include / ThenInclude chain cannot be represented with Expression<Func<T, object>>. This pattern was used in EF6 which supported a special syntax (Select) inside the include expression to resolve collection element. But EF Core does not support that out of the box.

The easiest and most natural way to plug EF Core pattern is to change the definition as follows:

List<Func<IQueryable<T>, IIncludableQueryable<T, object>>> Includes { get; }

Adding the sample for entity having User property having UserRoles collection having Role property would be like this:

Includes.Add(q => q.Include(e => e.User).ThenInclude(e => e.UserRoles).ThenInclude(e => e.Role));

And the corresponding part of the Specify method implementation would be:

var queryableResultWithIncludes = spec.Includes
    .Aggregate(query,
        (current, include) => include(current));
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • 1
    Nice approach. The only issue I see with this is the `IIncludableQueryable` interface will introduce a dependency to EF core. This will be an issue in a DDD situation where the domain shouldn't know much about EF core internals. – GETah Mar 05 '23 at 13:01
  • @GETah True. But that's what is given to us by EF Core. I guess they find the `Include` / `ThenInclude` more intuitive, also supporting filtered, ordered etc. includes, something not possible with `params Expression>` approach. And they do not care much about DDD purity (and they shouldn't - EF Core entities represent *data model*, which doesn't care about encapsulation, business logic and similar OOP). Anyway, I have this https://stackoverflow.com/questions/52997082/translating-generic-eager-load-method-from-ef6-to-ef-core/53053633#53053633 in case EF6 style is enough for you. – Ivan Stoev Mar 05 '23 at 13:33
  • I actually found a nice way around by specifying includes as string parameters. Using `nameof(_property_to_include_)` solves the problem in a nice way. – GETah Mar 10 '23 at 10:37
  • @GETah Ha, nice only if you have single level. In which case the original pattern with `params Expression>` works. The problem is with nested (`ThenInclude`) things, and especially with collections. Try expressing "Products.Orders.OrderDetails" with interpolated string and `nameof` and you'll see. – Ivan Stoev Mar 10 '23 at 10:53
  • `ThenInclude` can be covered by string includes as well :) So you can perform things like `"${nameof(Blog)},{nameof(Blog).nameof(Post)}` which is equivalent to .Include(b => b.blog).ThenInclude(b => b.Post) - I haven't compiled this though :p – GETah Mar 18 '23 at 09:09