C# 在表达式的帮助下重用搜索模式

问题描述 投票:0回答:1

具有以下查询模型:

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);
c# expression
1个回答
0
投票

这就是我想到的:

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
,这不适用于此。

© www.soinside.com 2019 - 2024. All rights reserved.