如何使用 Linq Skip 和 Take 调用 Expression<Func<T、bool>> 参数化函数



我有一个如下所示的函数。它具有Expression<Func<T, bool>>参数,其中 T 是名为"语言"的实体。

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

我想从我的 razor 页面调用此函数,以便我只能获取 10 到 15 的记录(而不是每条记录)。因此,此方法中存在Expression<Func<T, bool>>类型的"filter"参数。我想利用它。

所以从我的剃须刀页面上的 C# 代码。我可以像下面这样称呼它:

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

上面的代码会给我命名为"ABC"的语言。现在是修改部分。

我只需要 10 到 15 条记录,所以我需要修改它,包括 Linq 表达式上的 where 子句的Skip(skip).Take(pageSize)。问题是 - 这是可以做到的,那么如何做到?

context.Set<T>()是语言列表,因此我们可以跳过并采取,对吗?

我希望我能够正确地解释这个问题。

奇怪的是你不能修改你的MyFilterAsync,你确定吗?因为排序、跳过和获取的要求需要更多的论据,而不仅仅是filter。因此,最好能为MyFilterAsync编写更多重载,以接受更多参数并编写与其他用户建议的代码类似的代码。

然而,在这里,我试图让你的MyFilterAsync保持不变,但你仍然可以钩住排序、跳过和获取的逻辑。这根本不是什么神奇的东西,但你仍然需要编写其他代码:你自己的扩展方法来替换默认的Where。这取决于编译器如何选取扩展方法重载。默认值具有最通用的TEntity类型。你只需要使扩展方法重载在类型上更具体,例如:示例中的Languages类型。它可以是基本实体类型。当它不太通用(更具体)时,编译器将使用扩展重载,而不是默认重载。

以下是使其工作的方法:

//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;
}
}

现在是你如何使用它:

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

OrderBySkipTake被链接在filter上(使用我们的扩展方法),以便它们可以缓冲以供以后在我们自己的Where扩展方法中使用,我们可以在其中读取缓冲的表达式以按照我们想要的方式正确构建Where)。

注意:您应该将扩展类放在与System.LinqQueryable相同的命名空间中,以便您的扩展方法可以自动可用(当然将使用扩展方法而不是默认扩展方法)。

是的,只需在对项目进行排序后执行此操作,您可能需要为Name属性实现一个接口,以使 orderby 属性与泛型一起使用。

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();
}

最新更新