如何通过比较 Func 来创建 Expression
本文关键字:Func 创建 Expression TSource bool 何通过 Tsource int 比较 | 更新日期: 2023-09-27 17:56:51
我有一个包含大量 DbSet 的 DbContext。每个 DbSet 都应该有一个函数来从集合中获取项目页面,具有给定的 pageSize 并按特定的 sortOrder 排序。像这样:
var pageItems = dbContext.Posts
.Where(post => post.BlogId == blogId)
.OrderBy(some sortorder)
.Skip(pageNr * pageSize)
.Take(pageSize);
我希望能够使用我所有的 DbSet 执行此操作,因此我创建了一个扩展方法,其中一个参数指定要比较的外键,另一个参数指定此外键应具有的值。
public static IQueryable<TSource> GetPage<TSource>(this IQueryable<TSource> source,
int pageNr, int pageSize,
Expression<Func<TSource, Tproperty>> keySelector, Tproperty comparisonValue)
{
return source
.Where( ??? )
.OrderBy(some sortorder)
.Skip(pageNr * pageSize)
.Take(pageSize);
}
如何在适合 Where 的谓词中转换键选择器?
如何在适合 Where 的谓词中转换键选择器?
这很容易,但我不知道您将如何处理订购。无论如何,这是您可以执行所需操作的方法:
public static IQueryable<TSource> GetPage<TSource, TKey>(this IQueryable<TSource> source,
int pageNr, int pageSize,
Expression<Func<TSource, TKey>> keySelector, TKey comparisonValue)
{
var predicate = Expression.Lambda<Func<TSource, bool>>(
Expression.Equal(keySelector.Body, Expression.Constant(comparisonValue)),
keySelector.Parameters);
return source
.Where(predicate)
//.OrderBy(some sortorder) ??
.Skip(pageNr * pageSize)
.Take(pageSize);
}
你正在寻找一种方法来从Expression<Func<TSource, Tproperty>> keySelector
和Tproperty comparisonValue
获取Expression<Func<TSource, boolean>>
,以便实体框架可以将其转换为存储表达式。
这意味着琐碎的
public static Expression<Func<TSource, bool>> KeyPredicateNaive<TSource, Tproperty>(Expression<Func<TSource, Tproperty>> keySelector, Tproperty comparisonValue)
{
return (TSource source) =>EqualityComparer<Tproperty>.Default.Equals(keySelector.Compile()(source), comparisonValue);
}
行不通。这不能转换为商店表达式。
我们需要手动构造表达式。我们需要的是一个以键选择器作为其左值的相等表达式,以及一个将比较值作为其右值的常量表达式。我们可以这样构造:
public static Expression<Func<TSource, bool>> KeyPredicate<TSource, Tproperty>(Expression<Func<TSource, Tproperty>> keySelector, Tproperty comparisonValue)
{
var bd = Expression.Equal(keySelector.Body, Expression.Constant(comparisonValue));
return Expression.Lambda<Func<TSource, bool>>(bd, keySelector.Parameters);
}
其结果可以传递给您的 where 类。 精简(以便编译和运行),您的方法将如下所示
public static IQueryable<TSource> GetPage<TSource>(this IQueryable<TSource> source,
int pageSize,
Expression<Func<TSource, Tproperty>> keySelector, Tproperty comparisonValue)
{
return source
.Where(KeyPredicate(keySelector, comparisonValue)
.Take(pageSize);
}
我会用这个吗?应该不会。将谓词作为 lambda 直接传递给函数比自己构造表达式更容易。但这肯定是一种可能性。
试试这个,看看是否有帮助
Expression<Func<TSource, bool>> keySelector
或者干脆
Func<TSource, bool> keySelector
给定此代码:
sealed class SwapVisitor : ExpressionVisitor
{
private readonly Expression _from;
private readonly Expression _to;
public SwapVisitor(Expression from, Expression to)
{
_from = from;
_to = to;
}
public override Expression Visit(Expression node)
{
return node == _from ? _to : base.Visit(node);
}
}
static Expression<Func<TInput, bool>> Combine<TInput, TOutput>(
Expression<Func<TInput, TOutput>> transform,
Expression<Func<TOutput, bool>> predicate)
{
var swap = new SwapVisitor(predicate.Parameters[0], transform.Body);
return Expression.Lambda<Func<TInput, bool>>(
swap.Visit(predicate.Body), transform.Parameters);
}
您可以:
.Where(Combine(keySelector, key => key == comparisonValue))
所以这是创建一个 新Expression
,传递表达式的主体keySelector
和用于比较的新表达式。
感谢组合 lambda 表达式
您正在将扩展写入可查询源,是吗?因此,只需传递表达式并过滤源:
public static IQueryable<TSource> GetPage<TSource, TKey>(this IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
Expression<Func<TSource, TKey>> keySelector,
int pageNr, int pageSize
)
{
return source
.Where(predicate)
.OrderBy(keySelector)
.Skip(pageNr * pageSize)
.Take(pageSize);
}
用法:
db.Posts.GetPage(p => p.Author == "Bob", p => p.Date, 5, 10);
注意:在您的方法中,您在排序(第二个表达式)方面存在问题,您得到的只是p => p.Author, "Bob"
传递两个参数,而不是传递一个现成的表达式p => p.Author == "Bob"
。
但是我会将谓词和键选择器移出GetPage
方法。让此方法仅关注分页(如方法名称所述):
public static IQueryable<TSource> GetPage<TSource, TKey>(this IQueryable<TSource> source,
int pageNr, int pageSize)
{
return source.Skip(pageNr * pageSize).Take(pageSize);
}
用法:
db.Posts.Where(p => p.Author == "Bob").OrderBy(p => p.Date).GetPage(5, 10);
或者如果您有存储库
postsRepository.GetByAuthor("Bob").GetPage(5, 10);