yogihosting
yogihosting

Reputation: 6302

How to call Expression<Func<T, bool>> parameterized function wtih Linq Skip and Take

I have a function shown below. It has Expression<Func<T, bool>> parameter where T is the entity called "Languages".

public async Task<List<T>> MyFilterAsync(Expression<Func<T, bool>> filter)
{
    return await context.Set<T>().Where(filter).ToListAsync();
}

I want to call this function from my razor pages so that I can get records from 10 to 15 only (and not every record). So there is "filter" parameter of Expression<Func<T, bool>> type in this method. I want to make use of it.

So from C# code on my razor pages. I can call this like as shown below:

Expression<Func<Languages, bool>> filter = m => m.Name == "ABC";

The above code will give me lanaguages that have name "ABC". Now comes the modification part.

I want only 10 to 15 records so I need to modify it incude Skip(skip).Take(pageSize) for the where clause on Linq expression. The question is - is this can be done, so how?

The context.Set<T>() is a list of Languages so we can do skip and take, right?

I hope I am able to explain the question properly.

Upvotes: 2

Views: 5737

Answers (2)

King King
King King

Reputation: 63337

It's strange that you cannot modify your MyFilterAsync, are you sure? Because the requirement of ordering, skipping & taking need more arguments than just the filter. So it's best if you could write more overloads for your MyFilterAsync to accept more arguments and write similar code to what proposed by other users.

However here I'm trying to make it possible to keep your MyFilterAsync unchanged but still you can hook in the logic for ordering, skipping & taking. It's not magic at all but you still need to write other code: your own extension method to replace the default Where. It depends on how the extension method overloads are picked by the compiler. The default has the most generic type of TEntity for entitty type. You just need to make your extension method overload more specific on the type, e.g: the Languages type in your example. It can be your base entity type. When it's less general (more specific), your extension overloads will be used by the compiler instead of the default ones.

Here's how you can do to make it work:

//put this in the same namespace with the default 
//extension methods defined in System.Linq.Queryable
public static class YaQueryableExtensions
{
    static readonly AsyncLocal<int> _skip = new AsyncLocal<int>();
    static readonly AsyncLocal<int> _take = new AsyncLocal<int>();
    static class ExpressionBuffers<TEntity>
    {
        public static readonly AsyncLocal<Expression<Func<TEntity, object>>> OrderBy =
                               new AsyncLocal<Expression<Func<TEntity, object>>>();
    }

    //here is your own extension method for Where
    //targeting the specific type of Languages
    //which can be any base entity type (if you want it to apply on a broader scope)
    public static IQueryable<Languages> Where(this IQueryable<Languages> source,
                                              Expression<Func<Languages, bool>> filter)
    {
        return source.WhereWithExpressionBuffers(filter);
    }

    //the generic helper method which can be used on a specific closed type
    //of T (this method can be made private)
    public static IQueryable<T> WhereWithExpressionBuffers<T>(this IQueryable<T> source,
        Expression<Func<T, bool>> filter)
    {
        source = Queryable.Where(source, filter);
        //check for order-by (which should be chained first if any)
        var orderBy = ExpressionBuffers<T>.OrderBy.Value;
        if(orderBy != null)
        {
            source = source.OrderBy(orderBy);
            ExpressionBuffers<T>.OrderBy.Value = null;
        }
        //check for skip
        var skip = _skip.Value;
        if (skip > 0)
        {
            source = source.Skip(_skip.Value);
            _skip.Value = 0;
        }
        //check for take
        var take = _take.Value;
        if (take > 0)
        {
            source = source.Take(take);
            _take.Value = 0;
        }
        return source;
    }

    public static Expression<Func<T, bool>> Skip<T>(this Expression<Func<T, bool>> filter, int skip)
    {
        _skip.Value = skip;
        return filter;
    }
    public static Expression<Func<T, bool>> Take<T>(this Expression<Func<T, bool>> filter, int take)
    {
        _take.Value = take;
        return filter;
    }
    public static Expression<Func<TEntity, bool>> OrderBy<TEntity>(this Expression<Func<TEntity, bool>> filter, 
        Expression<Func<TEntity,object>> orderBy)
    {
        ExpressionBuffers<TEntity>.OrderBy.Value = orderBy;
        return filter;
    }
}

Now is how you use it:

var result = await MyFilterAsync(filter.OrderBy(e => e.Name).Skip(skip).Take(pageSize));

The OrderBy, Skip and Take are chained on the filter instead (with our extension methods) so that they can be buffered for later using inside our own Where extension method where we can read the buffered expressions to build up the Where correctly the way we want).

NOTE: you should put your extension class in the same namespace with Queryable which is System.Linq so that your extension methods can become available automatically (and of course will be used instead of the default extension methods).

Upvotes: 2

LazZiya
LazZiya

Reputation: 5719

Yes just do it after sorting the items, and you may need to implement an interface for Name property to have the orderby property work with generics.

public interface IHasName
{
    string Name { get; set; }
}
public async Task<List<T>> MyFilterAsync(Expression<Func<T, bool>> filter, int skip, int take)
where T : class, IHasName
{
return await context.Set<T>()
                    .Where(filter)
                    .OrderBy(x=> x.Name)
                    .Skip(skip)
                    .Take(take)
                    .ToListAsync();
}

Upvotes: 1

Related Questions