繁体   English   中英

EF Core 动态过滤器

[英]EF Core dynamic filter

我一直在使用表达式树在 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; }
}

我会很感激任何帮助。

主要问题不是类,而是你使用它的方式:

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> ,编译器会找到并使用IEnumerable<T>Where扩展方法(在Enumerable类中定义)。

这使得Where (以及所有以下方法,如果有)在执行和具体化Where之前的查询之后执行客户端(在您的情况下 - 整个表)。

返回 IEnumerable<T> 与 IQueryable<T>涵盖了IQueryable<T>IEnumerable<T>之间的区别。 您需要做就是确保始终使用Expression<Func<...>> IQueryable<T> IEnumerable<T> 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;
}

更新以包含更多数据类型:

private static Expression GetExpression(ParameterExpression param, Filter filter)
{
    // Defaults
    FilterOperation filterOp = FilterOperation.Equals;
    ConstantExpression constant = Expression.Constant(filter.Value);

    MemberExpression member = Expression.Property(param, filter.PropertyName);
    var type = member.Type;

    switch (type.FullName)
    {
        case "System.Guid":
            Guid outGuid;
            if (Utilities.IsGuid(filter.Value.ToString(), out outGuid))
                constant = Expression.Constant(outGuid);
            break;
        case "System.DateTime":
            if (Utilities.IsDate(filter.Value.ToString()))
                constant = Expression.Constant(Convert.ToDateTime(filter.Value));
            break;
        case "System.Single":
            if (Utilities.IsNumber(filter.Value.ToString()))
                constant = Expression.Constant(Convert.ToSingle(filter.Value));
            break;
        case "System.Int16":
            if (Utilities.IsNumber(filter.Value.ToString()))
                constant = Expression.Constant(Convert.ToInt16(filter.Value));
            break;
        case "System.Int32":
            if (Utilities.IsNumber(filter.Value.ToString()))
                constant = Expression.Constant(Convert.ToInt32(filter.Value));
            break;
        case "System.Double":
            if (Utilities.IsNumber(filter.Value.ToString()))
                constant = Expression.Constant(Convert.ToDouble(filter.Value));
            break;
        case "System.Decimal":
            if (Utilities.IsNumber(filter.Value.ToString()))
                constant = Expression.Constant(Convert.ToDecimal(filter.Value));
            break;
        case "System.Boolean":
            if (Utilities.isBoolean(filter.Value.ToString().ToLower()))
            {
                constant = Expression.Constant(Convert.ToBoolean(filter.Value));
                filterOp = FilterOperation.BooleanEquals;
            }
            break;
        case "System.String":
            constant = Expression.Constant(filter.Value);
            filterOp = FilterOperation.Contains;
            break;
    }

    switch (filterOp)
    {
        case FilterOperation.Equals:
            return Expression.Equal(member, constant);

        case FilterOperation.BooleanEquals:
            var valueExpression = Expression.Convert(constant, typeof(bool));
            return Expression.Equal(member, valueExpression);

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

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

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

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

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

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

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

    return null;
}

    public enum FilterOperation
    {
        Equals,
        BooleanEquals,
        GreaterThan,
        GreaterThanOrEqual,
        LessThan,
        LessThanOrEqual,
        Contains,
        StartsWith,
        EndsWith
    }

验证是可选的,但如果您需要它:


public static bool IsDate(object obj)
{
    if (obj == null) return false;

    try
    {
        string strDate = obj.ToString();

        DateTime dt = DateTime.Parse(strDate);
        if (dt != DateTime.MinValue && dt != DateTime.MaxValue)
            return true;
        return false;
    }
    catch
    {
        return false;
    }
}
public static bool IsNumber(string str)
{
    if (string.IsNullOrEmpty(str)) return false;

    bool isNum;
    double retNum;
    isNum = Double.TryParse(str, System.Globalization.NumberStyles.Any, System.Globalization.NumberFormatInfo.InvariantInfo, out retNum);
    return isNum;

}
public static bool IsGuid(string str, out Guid theGuid)
{
    if (string.IsNullOrEmpty(str))
    {
        theGuid = Guid.Empty;
        return false;
    }

    return Guid.TryParse(str, out theGuid);
}
public static bool isBoolean(string str)
{
    if (bool.TryParse(str, out _))
    {
        return true;
    }

    return false;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM