在 Entity Framework 查询中使用 MemberExpression。 Entity Framework 带有 MemberExpression 的 IQueryable 扩展
Use MemberExpression in Entity Framework query. Entity Framework IQueryable extension with MemberExpression
主线任务
为 Entity Framework 创建 IQueryable 扩展,其中可以使用 MemberExpression 设置查询中使用的字段。
WhereOverlap 是示例。 WhereOverlap 通过两个 DateTime 字段构建对实体的查询。
Orders 有 DateTime 字段 StartDate、EndDate。
Trips 有 DateTime 字段 From、To。
var dateFrom = DateTime.Now.AddDays(-30);
var dateTo = DateTime.Now.AddDays(-15);
var orders = await db.Orders
.WhereOverlap(
// MemberExpressions (Orders has StartDate, EndDate fields)
fromField: es => es.StartDate,
toField: es => es.EndDate,
from: dateFrom,
to: dateTo)
.ToListAsync();
var trips = await db.Trips
.WhereOverlap(
// MemberExpressions (Trips has From, To fields)
fromField: es => es.From,
toField: es => es.To,
from: dateFrom,
to: dateTo)
.ToListAsync();
要实现我们想要的,我们需要回答以下 2 个问题中的任何一个:
或问题 1
要创建此类 IQueryable 扩展,需要创建具有以下签名的 Where 扩展:
public static class QueryableWhereOverlapExtensions
{
static IQueryable<TEnt> Where<TEnt, TParam, TParam2>(this IQueryable<TEnt> source,
Expression<Func<TEnt, TParam>> property,
Expression<Func<TEnt, TParam2>> property2,
Expression<Func<TParam, TParam2, bool>> where)
{
// ??? HOW TO DO THIS
return ...
}
public static IQueryable<TEnt> WhereOverlap<TEnt>(this IQueryable<TEnt> source,
Expression<Func<TEnt, DateTime>> startField,
Expression<Func<TEnt, DateTime>> endField,
DateTime from, DateTime to)
{
// example of usage Where extension
return source.Where(startField, endField,
(start, end) => start <= to && end >= from);
}
}
如何实现带有显示签名的 IQueryable Where 扩展?
或问题 2
可以使用 A universal PredicateBuilder 构建动态查询形式的表达式 (Expression>)。
所以如果我们有这样的方法,我们将能够创建 WhereOverlap 扩展
static Expression<Func<TEnt, bool>> ApplayWhere<TEnt, TParam>(this Expression<Func<TEnt, TParam>> field,
Expression<Func<TParam, bool>> where)
{
// ??? HOW TO DO THIS
}
public static IQueryable<TEnt> WhereOverlap<TEnt>(this IQueryable<TEnt> source,
Expression<Func<TEnt, DateTime>> startField,
Expression<Func<TEnt, DateTime>> endField,
DateTime from, DateTime to)
{
// example of usage ApplayWhere
Expression<Func<TEnt, bool>> startExpr = startField.ApplayWhere(start => start <= to);
Expression<Func<TEnt, bool>> endExpr = endField.ApplayWhere(end => end >= from);
return source.Where(PredicateBuilder.And(startExpr, endExpr));
}
如何实现AppplayWhere?
这不完全是一个答案。但是我创建了 WhereOverlap 扩展,并提供了一种创建可重用 linq 查询的方法,可以使用 MemberExpression 来设置查询中使用的字段。
首先我们需要 MemberExpressionExtensions:
public static class MemberExpressionExtensions {
public static Expression<Func<TEnt, bool>> HasVal<TEnt, TProp>(this Expression<Func<TEnt, TProp?>> field) where TProp : struct
=> Expression.Lambda<Func<TEnt, bool>>(Expression.NotEqual(field.Body, Expression.Constant(null, typeof(TProp?))), field.Parameters);
public static Expression<Func<TEnt, bool>> HasNoVal<TEnt, TProp>(this Expression<Func<TEnt, TProp?>> field) where TProp : struct
=> Expression.Lambda<Func<TEnt, bool>>(Expression.Equal(field.Body, Expression.Constant(null, typeof(TProp?))), field.Parameters);
public static Expression<Func<TEnt, bool>> LessOrEqual<TEnt, TProp>(this Expression<Func<TEnt, TProp>> field, TProp val)
=> Expression.Lambda<Func<TEnt, bool>>(Expression.LessThanOrEqual(field.Body, Expression.Constant(val, typeof(TProp))), field.Parameters);
public static Expression<Func<TEnt, bool>> Less<TEnt, TProp>(this Expression<Func<TEnt, TProp>> field, TProp val)
=> Expression.Lambda<Func<TEnt, bool>>(Expression.LessThan(field.Body, Expression.Constant(val, typeof(TProp))), field.Parameters);
public static Expression<Func<TEnt, bool>> GreaterOrEqual<TEnt, TProp>(this Expression<Func<TEnt, TProp>> field, TProp val)
=> Expression.Lambda<Func<TEnt, bool>>(Expression.GreaterThanOrEqual(field.Body, Expression.Constant(val, typeof(TProp))), field.Parameters);
public static Expression<Func<TEnt, bool>> Greater<TEnt, TProp>(this Expression<Func<TEnt, TProp>> field, TProp val)
=> Expression.Lambda<Func<TEnt, bool>>(Expression.GreaterThan(field.Body, Expression.Constant(val, typeof(TProp))), field.Parameters);
}
现在我们可以创建可重复使用的查询构建器
class BigPayFilter {
readonly decimal Limit;
public BigPayFilter(decimal limit) {
Limit = limit;
}
public Expression<Func<TEnt, bool>> Create<TEnt>(
Expression<Func<TEnt, decimal>> field) {
// GreaterOrEqual is extension
return field.GreaterOrEqual(Limit);
}
}
用法。
例如,有 Payout 和 Premium 对象:
class Payout {
public decimal Total { get; set; }
}
class Premium {
public decimal Sum { get; set; }
}
// filter to find payments greater or equal 1000
//
// you can get limit value from configuration (in this example limit is 1000),
// and put BigPayFilter to IoC-container
var bigPayFilter = new BigPayFilter(1000);
// use BigPayFilter for payouts
var payoutPredicate =
bigPayFilter.Create<Payout>(pp => pp.Total);
var payouts = new[] {
new Payout{ Total = 100 },
new Payout{ Total = 50 },
new Payout{ Total = 25.5m },
new Payout{ Total = 1050.67m }
}
.AsQueryable()
.Where(payoutPredicate)
.ToList();
// use BigPayFilter for premiums
var premiumPredicate =
bigPayFilter.Create<Premium>(pp => pp.Sum);
var premiums = new[] {
new Premium{ Sum = 2000 },
new Premium{ Sum = 50.08m },
new Premium{ Sum = 25.5m },
new Premium{ Sum = 1070.07m }
}
.AsQueryable()
.Where(premiumPredicate)
.ToList();
对于更复杂的查询,您必须将 MemberExpressionExtensions 与由 Pete Montgomery 创建的“A universal PredicateBuilder”结合使用。
此解决方案完全支持 Entity Framework,包括异步操作。
主线任务
为 Entity Framework 创建 IQueryable 扩展,其中可以使用 MemberExpression 设置查询中使用的字段。
WhereOverlap 是示例。 WhereOverlap 通过两个 DateTime 字段构建对实体的查询。
Orders 有 DateTime 字段 StartDate、EndDate。
Trips 有 DateTime 字段 From、To。
var dateFrom = DateTime.Now.AddDays(-30);
var dateTo = DateTime.Now.AddDays(-15);
var orders = await db.Orders
.WhereOverlap(
// MemberExpressions (Orders has StartDate, EndDate fields)
fromField: es => es.StartDate,
toField: es => es.EndDate,
from: dateFrom,
to: dateTo)
.ToListAsync();
var trips = await db.Trips
.WhereOverlap(
// MemberExpressions (Trips has From, To fields)
fromField: es => es.From,
toField: es => es.To,
from: dateFrom,
to: dateTo)
.ToListAsync();
要实现我们想要的,我们需要回答以下 2 个问题中的任何一个:
或问题 1
要创建此类 IQueryable 扩展,需要创建具有以下签名的 Where 扩展:
public static class QueryableWhereOverlapExtensions
{
static IQueryable<TEnt> Where<TEnt, TParam, TParam2>(this IQueryable<TEnt> source,
Expression<Func<TEnt, TParam>> property,
Expression<Func<TEnt, TParam2>> property2,
Expression<Func<TParam, TParam2, bool>> where)
{
// ??? HOW TO DO THIS
return ...
}
public static IQueryable<TEnt> WhereOverlap<TEnt>(this IQueryable<TEnt> source,
Expression<Func<TEnt, DateTime>> startField,
Expression<Func<TEnt, DateTime>> endField,
DateTime from, DateTime to)
{
// example of usage Where extension
return source.Where(startField, endField,
(start, end) => start <= to && end >= from);
}
}
如何实现带有显示签名的 IQueryable Where 扩展?
或问题 2
可以使用 A universal PredicateBuilder 构建动态查询形式的表达式 (Expression
static Expression<Func<TEnt, bool>> ApplayWhere<TEnt, TParam>(this Expression<Func<TEnt, TParam>> field,
Expression<Func<TParam, bool>> where)
{
// ??? HOW TO DO THIS
}
public static IQueryable<TEnt> WhereOverlap<TEnt>(this IQueryable<TEnt> source,
Expression<Func<TEnt, DateTime>> startField,
Expression<Func<TEnt, DateTime>> endField,
DateTime from, DateTime to)
{
// example of usage ApplayWhere
Expression<Func<TEnt, bool>> startExpr = startField.ApplayWhere(start => start <= to);
Expression<Func<TEnt, bool>> endExpr = endField.ApplayWhere(end => end >= from);
return source.Where(PredicateBuilder.And(startExpr, endExpr));
}
如何实现AppplayWhere?
这不完全是一个答案。但是我创建了 WhereOverlap 扩展,并提供了一种创建可重用 linq 查询的方法,可以使用 MemberExpression 来设置查询中使用的字段。
首先我们需要 MemberExpressionExtensions:
public static class MemberExpressionExtensions {
public static Expression<Func<TEnt, bool>> HasVal<TEnt, TProp>(this Expression<Func<TEnt, TProp?>> field) where TProp : struct
=> Expression.Lambda<Func<TEnt, bool>>(Expression.NotEqual(field.Body, Expression.Constant(null, typeof(TProp?))), field.Parameters);
public static Expression<Func<TEnt, bool>> HasNoVal<TEnt, TProp>(this Expression<Func<TEnt, TProp?>> field) where TProp : struct
=> Expression.Lambda<Func<TEnt, bool>>(Expression.Equal(field.Body, Expression.Constant(null, typeof(TProp?))), field.Parameters);
public static Expression<Func<TEnt, bool>> LessOrEqual<TEnt, TProp>(this Expression<Func<TEnt, TProp>> field, TProp val)
=> Expression.Lambda<Func<TEnt, bool>>(Expression.LessThanOrEqual(field.Body, Expression.Constant(val, typeof(TProp))), field.Parameters);
public static Expression<Func<TEnt, bool>> Less<TEnt, TProp>(this Expression<Func<TEnt, TProp>> field, TProp val)
=> Expression.Lambda<Func<TEnt, bool>>(Expression.LessThan(field.Body, Expression.Constant(val, typeof(TProp))), field.Parameters);
public static Expression<Func<TEnt, bool>> GreaterOrEqual<TEnt, TProp>(this Expression<Func<TEnt, TProp>> field, TProp val)
=> Expression.Lambda<Func<TEnt, bool>>(Expression.GreaterThanOrEqual(field.Body, Expression.Constant(val, typeof(TProp))), field.Parameters);
public static Expression<Func<TEnt, bool>> Greater<TEnt, TProp>(this Expression<Func<TEnt, TProp>> field, TProp val)
=> Expression.Lambda<Func<TEnt, bool>>(Expression.GreaterThan(field.Body, Expression.Constant(val, typeof(TProp))), field.Parameters);
}
现在我们可以创建可重复使用的查询构建器
class BigPayFilter {
readonly decimal Limit;
public BigPayFilter(decimal limit) {
Limit = limit;
}
public Expression<Func<TEnt, bool>> Create<TEnt>(
Expression<Func<TEnt, decimal>> field) {
// GreaterOrEqual is extension
return field.GreaterOrEqual(Limit);
}
}
用法。 例如,有 Payout 和 Premium 对象:
class Payout {
public decimal Total { get; set; }
}
class Premium {
public decimal Sum { get; set; }
}
// filter to find payments greater or equal 1000
//
// you can get limit value from configuration (in this example limit is 1000),
// and put BigPayFilter to IoC-container
var bigPayFilter = new BigPayFilter(1000);
// use BigPayFilter for payouts
var payoutPredicate =
bigPayFilter.Create<Payout>(pp => pp.Total);
var payouts = new[] {
new Payout{ Total = 100 },
new Payout{ Total = 50 },
new Payout{ Total = 25.5m },
new Payout{ Total = 1050.67m }
}
.AsQueryable()
.Where(payoutPredicate)
.ToList();
// use BigPayFilter for premiums
var premiumPredicate =
bigPayFilter.Create<Premium>(pp => pp.Sum);
var premiums = new[] {
new Premium{ Sum = 2000 },
new Premium{ Sum = 50.08m },
new Premium{ Sum = 25.5m },
new Premium{ Sum = 1070.07m }
}
.AsQueryable()
.Where(premiumPredicate)
.ToList();
对于更复杂的查询,您必须将 MemberExpressionExtensions 与由 Pete Montgomery 创建的“A universal PredicateBuilder”结合使用。
此解决方案完全支持 Entity Framework,包括异步操作。