EF Core动态过滤器

时间:2018-12-14 05:24:25

标签: c# linq expression-trees ef-core-2.1

我一直在使用Expression树在EF Core查询的动态过滤器类中工作,一切看起来都很好,过滤器正在工作,我可以通过过滤器集合并起作用,但是当我看一下SQL语句时,它是查询整个表并将筛选器应用于结果集合,这是我的课程...

   public static class QueryExpressionBuilder
{
    private static readonly MethodInfo ContainsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
    private static readonly MethodInfo StartsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
    private static readonly MethodInfo EndsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });

    #region DynamicWhere

    /// <summary>Where expression generator.</summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="filters">The filters.</param>
    /// <returns></returns>
    public static Expression<Func<T, bool>> GetExpression<T>(IList<Filter> filters)
    {
        if (filters.Count == 0)
            return null;

        ParameterExpression param = Expression.Parameter(typeof(T), "t");
        Expression exp = null;

        if (filters.Count == 1)
            exp = GetExpression(param, filters[0]);
        else if (filters.Count == 2)
            exp = GetExpression<T>(param, filters[0], filters[1]);
        else
        {
            while (filters.Count > 0)
            {
                var f1 = filters[0];
                var f2 = filters[1];

                if (exp == null)
                    exp = GetExpression<T>(param, filters[0], filters[1]);
                else
                    exp = Expression.AndAlso(exp, GetExpression<T>(param, filters[0], filters[1]));

                filters.Remove(f1);
                filters.Remove(f2);

                if (filters.Count == 1)
                {
                    exp = Expression.AndAlso(exp, GetExpression(param, filters[0]));
                    filters.RemoveAt(0);
                }
            }
        }

        return Expression.Lambda<Func<T, bool>>(exp, param);
    }

    /// <summary>Comparision operator expression generator.</summary>
    /// <param name="param">The parameter.</param>
    /// <param name="filter">The filter.</param>
    /// <returns></returns>
    private static Expression GetExpression(ParameterExpression param, Filter filter)
    {
        MemberExpression member = Expression.Property(param, filter.PropertyName);
        var type = member.Type;
        ConstantExpression constant;
        switch (type.Name)
        {
            case "Int32":
                constant = Expression.Constant(Convert.ToInt32(filter.Value));
                break;
            case "String":
            default:
                constant = Expression.Constant(filter.Value);
                break;
        }

        // ConstantExpression constant = Expression.Constant(filter.Value);

        switch (filter.Operation)
        {
            case Op.Equals:
                return Expression.Equal(member, constant);

            case Op.GreaterThan:
                return Expression.GreaterThan(member, constant);

            case Op.GreaterThanOrEqual:
                return Expression.GreaterThanOrEqual(member, constant);

            case Op.LessThan:
                return Expression.LessThan(member, constant);

            case Op.LessThanOrEqual:
                return Expression.LessThanOrEqual(member, constant);

            case Op.Contains:
                return Expression.Call(member, ContainsMethod, constant);

            case Op.StartsWith:
                return Expression.Call(member, StartsWithMethod, constant);

            case Op.EndsWith:
                return Expression.Call(member, EndsWithMethod, constant);
        }

        return null;
    }

    /// <summary>And logic connector expression generator.</summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="param">The parameter.</param>
    /// <param name="filter1">The filter1.</param>
    /// <param name="filter2">The filter2.</param>
    /// <returns></returns>
    private static BinaryExpression GetExpression<T>(ParameterExpression param, Filter filter1, Filter filter2)
    {
        var bin1 = GetExpression(param, filter1);
        var bin2 = GetExpression(param, filter2);

        return Expression.AndAlso(bin1, bin2);
    }

    #endregion

}

}

要调用此类,我要做这样的事情:

var whereDeleg = QueryExpressionBuilder.GetExpression<Tax>(filters).Compile();
var myList = _dbContext.MyEntity.Where(whereDeleg).ToList();

我传递的滤镜参数是此类的集合:

public class Filter
{
    public string PropertyName { get; set; }
    public Op Operation { get; set; }
    public object Value { get; set; }
}

我将不胜感激。

1 个答案:

答案 0 :(得分:4)

主要问题不是类,而是使用方法:

var whereDeleg = QueryExpressionBuilder.GetExpression<Tax>(filters).Compile();
var myList = _dbContext.MyEntity.Where(whereDeleg).ToList();

您正在从方法中提取Expression<Func<T, bool>>,但是随后Complie()调用将其转换为Func<T, bool>。因此,尽管_dbContext.MyEntityIQueryable<T>,但是没有IQueryable<T>扩展方法Where占用Func<T, bool>(它们全部占用Expression<Func<T, bool>>)。但是由于IQueryable<T>继承了IEnumerable<T>(因此是Where),因此编译器为IEnumerable<T>(在Enumerable类中定义)使用Where扩展方法。

这将使Where(以及以下所有方法,如果有的话)在IQueryable<T>之前(在您的情况下是整个表格)执行并具体化查询之后,才执行客户端。

Returning IEnumerable<T> vs. IQueryable<T>涵盖了IEnumerable<T>IQueryable<T>之间的差异。您需要做的是确保始终使用IEnumerable<T>而不是使用相同名称和类似 looking 参数的Expression<Func<...>>扩展方法而不是Func<...>方法Compile

话虽如此,您应该直接使用方法结果,而无需调用var predicate = QueryExpressionBuilder.GetExpression<Tax>(filters); var myList = _dbContext.MyEntity.Where(predicate).ToList();

var myList = _dbContext.MyEntity.Where(QueryExpressionBuilder.GetExpression<Tax>(filters)).ToList();

或者只是

QueryExpressionBuilder

甚至更好的是,将以下自定义扩展方法添加到public static IQueryable<T> Where<T>(this IQueryable<T> source, IList<Filter> filters) { var predicate = GetExpression<T>(filters); return predicate != null ? source.Where(predicate) : source; } 类中:

var myList = _dbContext.MyEntity.Where(filters).ToList();

能够简单地使用(并最大程度地减少犯错误的可能性):

filters

侧面说明:主表达式构建器方法的实现过于复杂,并且还会破坏传递的输入public static Expression<Func<T, bool>> GetExpression<T>(IEnumerable<Filter> filters) { var param = Expression.Parameter(typeof(T), "t"); var body = filters .Select(filter => GetExpression(param, filter)) .DefaultIfEmpty() .Aggregate(Expression.AndAlso); return body != null ? Expression.Lambda<Func<T, bool>>(body, param) : null; } 列表。可以简化如下(没有上述缺陷):

$datas = Schools::orderBy('id','desc')
    ->whereBetween('created_at', ['2018-06-27', '2018-06-28'])
    ->get()