fluxera/Fluxera.Repository

Add support for EFCore Include() Linq extension.

Closed this issue · 5 comments

Maybe we allow the abstaction to leak in the Specification or the QueryOptions. Either way, we need to extend one of them to support storage specific Linq extension methods.

After the integration of the LiteDB.Queryable package, which supports Include extensions, we can refactor the LiteRepository to use the LinqRepository as ist base class. Then we can extend the IQueryOptions with an Include operation.

I'm just also pasting this here because i'm not sure if you see things in the comments of commits:

Maybe you might find this helpful for your .Include Queries - I do something like this:

    protected sealed override IAsyncEnumerable<TProjected> FindManyAsync<TProjected>(
        EfTrackingOptions asNoTracking,
        Expression<Func<TEntity, bool>> whereExpression,
        Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> includeExpression,
        Expression<Func<TEntity, TProjected>> projectExpression,
        CancellationToken cancellationToken = default)
    {
        return this.ToListAsync(
            this.Queryable.Apply(asNoTracking)
                .Apply(whereExpression)
                .Apply(includeExpression)
                .ToProjection(projectExpression),
            cancellationToken);
    }

Then you can write the include statement like this:

        Func<IQueryable<Book>, IIncludableQueryable<Book, object>> include = a => 
a.Include(i => i.BookAuthors).ThenInclude(_ => _.Author);

        var randomBook = this.GetRandomBook();
        var shouldIncludeRelations = this._bookRepository.GetById(EfTrackingOptions.WithTracking,
 randomBook.Id, include );

or like this:

       IIncludableQueryable<Book, object> IncludeBookAuthorsAndAuthors(IQueryable<Book> a)
        {
            return a.Include(i => i.BookAuthors)
                .ThenInclude(_ => _.Author);
        }

        var randomBook = this.GetRandomBook();
        var shouldIncludeRelations = this._bookRepository
.GetById(EfTrackingOptions.WithTracking, randomBook.Id, IncludeBookAuthorsAndAuthors);

This way that I shared can be improved in my opinion - I would like to put it into an ISpecification<T> and/or make some sort of fluent builder interface so you could do something like:

var includeStatement = RelationsBuilder.Initialize<Book>()
.Include(i => i.BookAuthors)
.ThenInclude(_ => _.Author);

This way its easier to type. I think including it in a ISpecification<T> is probably a good solution

If your curious, the .Apply code for the include statement is this:

 public static IQueryable<T> Apply<T>(
        this IQueryable<T> queryable,
        Func<IQueryable<T>, IIncludableQueryable<T, object>> includeExpression)
    {
        return includeExpression(queryable);
    }

I just wanted to share this! Thanks!

Is there any reason why you didn't use IIncludableQueryable<T,K> in your implementation? Just curious

Is there any reason why you didn't use IIncludableQueryable<T,K> in your implementation? Just curious

Mostly to keep the IQueryable away from the user and the fact that not every storage may support LINQ/IQueryable.

Mostly to keep the IQueryable away from the user and the fact that not every storage may support LINQ/IQueryable.

Both of those are very good reasons, now I realize my error with allowing IIncludableQueryable<T,K> to leak haha, I will need to fix that. Also very good point about storage providers and IQueryable.

Good thinking of course!