简体   繁体   中英

Applying IEnumerable<'T>.OrderBy on a IQueryable<'T>

I've got a custom type which I use on my repositories that helps me explicitly represent query options, eg for sorting, paging etc.

Initially, the interface looked like this:

public class IQueryAction<TEntity> {
    IQueryable<TEntity> ApplyTo(IQueryable<T> entitity);
}

With that I can represent sorting like this:

public class SortingAction<TEntity, TSortKey> : IQueryAction<TEntity>
{
    public Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> SortAction { get; }

    public SortingAction(Expression<Func<TEntity, TSortKey>> sortExpression) {
        SortAction = q => q.OrderBy(sortExpression);
    }

    public IQueryable<TEntity> ApplyTo(IQueryable<TEntity> query) {
        return SortAction(query);
    }

}

Most of the time I work with Entity Framework, so this is not a problem. However now I need to implement the IQueryAction<'T> model for a data source which doesn't provide a query provider ( IQueryProvider ). I could now refactor my interface to use IEnumerable<'T> instead of IQueryable<'T> and expect a Func delegate for the key selector instead of a lambda expression .

Now my question is: Would this cause the sort action to be run in memory instead of on the query provider when an IQueryable<'T> is simply being casted to an IEnumerable<'T> ? Since I wouldn't pass an expression anymore, but rather a delegate , how could the query provider still know what key I'd like to use for the query? And if that won't work anymore: What could I do to fit both, sorting in memory and on the query provider, depending on the underlying type of IEnumerable<'T> ?

Usage example

public class MyEntity {
    public int    Id   { get; set; }
    public string Name { get; set }
}

// somewhere in code
var orderByName = new SortingAction<MyEntity, string>(x => x.Name);
myEntityRepository.GetAll(orderByName);

// ef repository impl
public IEnumerable<TEntity> GetAll(IQueryAction<TEntity> queryAction) {
    return queryAction.ApplyTo(dbContext.Set<TEntity>()); // runs on the query provider
}

// misc repository impl not supporting IQueryable/IQueryProvider
public IEnumerable<TEntity> GetAll(IQueryAction<TEntity> queryAction) {
    var result = someProvider.Sql("SELECT *")...
    return queryAction.ApplyTo(result);
}

What could I do to fit both, sorting in memory and on the query provider

Leave IQueryAction<TEntity> as is, and convert IEnumerable<T> to IQueryable<T> , using AsQueryable extension:

var list = new List<YourEntity> { /* ... */ };
var queryableList = list.AsQueryable();

sortingAction.ApplyTo(queryableList);

Ultimately this will do in-memory sorting for in-memory sequences (like collections), and it will send queries against databases for "true" queryables.

Would this cause the sort action to be run in memory instead of on the query provider when an IQueryable<'T> is simply being casted to an IEnumerable<'T>?

Yes, because you will be binding to the Enumerable extension methods which do sorting in-memory. It doesn't matter at that point what the underlying provider is; the Enumerable extension methods will enumerate the underlying collection and "sort" it in-memory.

What could I do to fit both, sorting in memory and on the query provider, depending on the underlying type of IEnumerable<T> ?

The client can call ToQueryable() before calling the methods. If the underlying collection is already an IQueryable<T> then the input is just cast; otherwise an adapter is placed on top of the IEnumerable to adapt the IQueryable methods to IEnumerable methods

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