具有以下查询模型:
public class ProductSearch
{
public DateTimeOffset? From { get; set; }
public DateTimeOffset? To { get; set; }
}
ASP.NET Core 操作方法使用此类来查询表:
var query = _dbContext.Products.AsNoTracking();
if (search.From > search.To)
{
// just ignore the 'To' value
search.To = null;
}
if (search.From.HasValue && search.From == search.To)
{
query = query.Where(x => x.CreationTime.UtcDateTime == search.From.Value.UtcDateTime);
}
else
{
if (search.From.HasValue)
{
query = query.Where(x => x.CreationTime.UtcDateTime >= search.From.Value.UtcDateTime);
}
if (search.To.HasValue)
{
query = query.Where(x => x.CreationTime.UtcDateTime <= search.To.Value.UtcDateTime);
}
}
这种模式经常发生,具有不同的列和类型。例如,还有
PriceFrom
和 PriceTo
属性具有相同的逻辑,只是不同的列和类型。
如何使用表达式提取此样板代码,以便将其应用于任何类型的任何列(使用
IQueryable<T>
)?
示例:
public class ProductSearch
{
[DBRangeFilter(ColumnName = "CreationTime", Limit = RangeLimit.LowerLimit)]
public DateTimeOffset? From { get; set; }
[DBRangeFilter(ColumnName = "CreationTime", Limit = RangeLimit.UpperLimit)]
public DateTimeOffset? To { get; set; }
}
行动方法:
query = query.ApplyRangeFilters(search);
这就是我想到的:
public static IQueryable<TEntity> ApplyRangeFilter<TEntity>(
this IQueryable<TEntity> source,
object? from,
object? to,
string columnName)
{
if (from is not null && from.Equals(to))
{
var lambda = GetLambda<TEntity>(columnName, from, 0);
source = source.Where(lambda);
}
else
{
if (from is not null)
{
var lambda = GetLambda<TEntity>(columnName, from, 1);
source = source.Where(lambda);
}
if (to is not null)
{
var lambda = GetLambda<TEntity>(columnName, to, -1);
source = source.Where(lambda);
}
}
return source;
}
private static Expression<Func<TEntity, bool>> GetLambda<TEntity>(
string columnName,
object value,
int comparison)
{
var param = Expression.Parameter(typeof(TEntity));
var left = Expression.Property(param, columnName);
var right = Expression.Constant(value, value.GetType());
var comp = comparison switch
{
> 0 => Expression.GreaterThanOrEqual(left, right),
< 0 => Expression.LessThanOrEqual(left, right),
_ => Expression.Equal(left, right)
};
return Expression.Lambda<Func<TEntity, bool>>(comp, param);
}
可以这样使用:
query = query.ApplyRangeFilter(search.PriceFrom, search.PriceTo, nameof(Product.Price));
我想自动检测并应用
*From
和 *To
属性对,所以我创建了这个:
public static IQueryable<TEntity> ApplyRangeFilters<TEntity>(
this IQueryable<TEntity> source,
object search)
{
var properties = search.GetType().GetProperties();
var filterPropNames = properties
.Where(p => p.Name.EndsWith("From"))
.Select(p => (Name: p.Name[..^4], Type: p.PropertyType))
.Where(nameType => properties.Any(p => p.Name == $"{nameType.Name}To" && p.PropertyType == nameType.Type))
.Select(x => x.Name);
foreach (var propName in filterPropNames)
{
var fromVal = properties.First(p => p.Name == $"{propName}From").GetValue(search, null);
var toVal = properties.First(p => p.Name == $"{propName}To").GetValue(search, null);
source = source.ApplyRangeFilter(fromVal, toVal, propName);
}
return source;
}
可以这样使用:
query = query.ApplyRangeFilters(search);
这对于我的所有整数和小数类型都适用,但我的日期查询通常包含
.UtcDateTime
,这不适用于此。