【问题标题】:Refactor filters with EntityFramework使用 EntityFramework 重构过滤器
【发布时间】:2014-08-16 22:04:51
【问题描述】:

我遇到的问题是,我想通过 Asp.net MVC 4 使用 EntityFramework 5 以一种很好的方式在应用程序中实现过滤。

过滤器是由这样的 json 字符串提供的:

filter  
[   {"type":"string","value":"o","field":"name"},
    {"type":"list","value":[1,2,5],"field":"type"},
    {"type":"boolean","value":true,"field":"active"},   
    {"type":"numeric","comparison":"gt","value":0,"field":"order"},
    {"type":"date","comparison":"lt","value":"11/28/2014","field":"updated_at"}
]

(其实就是ExtJs的网格过滤字符串)

我以这种方式实现了过滤器层次结构:FilterBase 类,然后是继承自 FilterBaseBooleanFilterStringFilterComparisonFilterBase,然后是 DateFilterNumericFilter

过滤器基础如下所示:

public abstract class FilterBase
{
    public string Field { get; set; }
}

/// <summary>
/// Filter Base Class
/// </summary>
/// <typeparam name="T">The value type</typeparam>
/// <typeparam name="T2">The entity property's type</typeparam>
public abstract class FilterBase<T,T2>:FilterBase
{
    public T Value { get; set; }

    /// <summary>
    /// Apply the filter
    /// </summary>
    /// <typeparam name="T1">The query main type, for instance, User if we are filtering users</typeparam>
    /// <param name="query">The query over to make the filter</param>
    /// <param name="field">A function that retrieves the main property's value</param>
    /// <returns>The new Query</returns>
    public abstract IQueryable<T1> Apply<T1>(IQueryable<T1> query, Func<T1, T2> field);
}

我的想法是在每个类中定义完成比较的方式。但为此,我需要知道要过滤的实体字段。这就是Apply 方法的field 参数的原因。例如:

foreach (var filterBase in filters)
{
    switch (filterBase.Field.ToLower())
    {
        case "name":
            query = ((StringFilter)filterBase).Apply(query, u => u.UserFirstName);
            break;
        case "lastname":
            query = ((StringFilter) filterBase).Apply(query, u => u.UserLastName);
            break;
        case "login":
            query = ((StringFilter)filterBase).Apply(query, u => u.UserLogin);
            break;
        case "role":
            query = ((StringFilter)filterBase).Apply(query, u => u.UserRole.Name);
            break;
        case "email":
            query = ((StringFilter)filterBase).Apply(query, u => u.UserEmail);
            break;
        case "active":
            query = ((BooleanFilter)filterBase).Apply(query, u => u.Active);
            break;
    }
}

例如,这可能是NumericFilter 类:

public class NumericFilter : ComparisonFilterBase<float, float>
{
    public override IQueryable<T1> Apply<T1>(IQueryable<T1> query, Func<T1, float> field)
    {
        switch (Comparison)
        {
            case Comparison.GreaterThan:
                return query.Where(t => field(t) > Value);
            case Comparison.LowerThan:
                return query.Where(t => field(t) < Value);
            case Comparison.Equals:
                return query.Where(t => field(t) == Value); 
        }
        return query;
    }
}

这里的问题是 Linq to Entities 不适用于函数 field 的评估。我知道对于实例,此查询有效:query = query.Where(t =&gt; t.Age &gt; Value);,唯一的问题是对于ExpressionTreefield 参数是一个函数,而不是对象访问部分。

我能做些什么来解决这个问题?

【问题讨论】:

    标签: c# asp.net-mvc linq entity-framework linq-to-entities


    【解决方案1】:

    你快到了。只需使用Expression&lt;Func&lt;T, Type&gt;&gt; 而不是Func&lt;T,Type&gt;。 像这样的:

       public static IQueryable<T1> Apply<T1, TField>(IQueryable<T1> query, Expression<Func<T1, TField>> field, TField value, Comparison compare)
        {
            ExpressionType expressionType;
            ConstantExpression searchValue = Expression.Constant(value);
            ParameterExpression parameter = field.Parameters.First();
            Expression body;
            if (!Enum.TryParse(Enum.GetName(typeof(Comparison),compare), true, out expressionType)) 
            {
                //probably string: StartsWith, EndsWith, Contains
                MethodInfo stringMethod = GetStringMethodInfo(compare);
                body = Expression.Call(field.Body, stringMethod, searchValue);
            }
            else
            {
                body = Expression.MakeBinary(expressionType, field, searchValue);
            }
            Expression<Func<T1, bool>> predicate = Expression.Lambda<Func<T1, bool>>(body, parameter);
            return query.Where(predicate);
        }
    
        private static MethodInfo GetStringMethodInfo(Comparison comparer)
        {
            string methodName = Enum.GetName(typeof(Comparison), comparer);
            return
                typeof(string).GetMethods()
                    .FirstOrDefault(m => m.Name.Equals(methodName) && m.GetParameters().Count() == 1);
        }
    
        public enum Comparison
        {
            GreaterThan = ExpressionType.GreaterThan,
    
            GreaterThanOrEqual = ExpressionType.GreaterThanOrEqual,
    
            LessThan = ExpressionType.LessThan,
    
            LessThanOrEqual = ExpressionType.LessThanOrEqual,
    
            Equals = ExpressionType.Equal,
    
            NotEqual = ExpressionType.NotEqual,
    
            StartsWith,
    
            EndsWith,
    
            Contains
        }
    

    编辑:根据注释修正代码中的错误

    【讨论】:

    • 您好,感谢您的回答,它似乎是在正确的方向。我在我的代码中使用了上面的想法,但是在Expression.Call(field, stringMethod, searchValue);....行中创建正文时出现此错误。错误:Method 'Boolean Contains(System.String)' declared on type 'System.String' cannot be called with instance of type 'System.Func``2。似乎它试图在函数中评估字符串方法,而不是在函数体中。你怎么看?
    • 我找到了解决方案:使用field.Body,其他错误在参数中,在字段表达式中有一个名称,我们创建了另一个名称,解决这个问题:ParameterExpression parameter = field.Parameters.FirstOrDefault();
    • 对不起,我会相应地更新代码。很高兴它有帮助。
    【解决方案2】:

    此答案仅用于显示最终解决方案,也许对其他人有所帮助。这是基于:user3411327 接受的答案。这是最终的字符串过滤器Apply 方法,Value 字段继承自基本过滤器类。

            public override IQueryable<T1> Apply<T1>(IQueryable<T1> query, Expression<Func<T1, string>> field)
            {   
                //create the constant expression for the value
                ConstantExpression searchValue = Expression.Constant(Value);
                //create the expression for the parameter
    
    //            ParameterExpression parameter = Expression.Parameter(typeof(T1), "t");
                ParameterExpression parameter = field.Parameters.FirstOrDefault();
    
                //create the body -> it is calling the method contains from the field in the field expression, and the value in searchValue
                MethodInfo stringMethod = typeof(string).GetMethods().FirstOrDefault(m => m.Name.Equals("Contains") && m.GetParameters().Count() == 1);
                Expression body = Expression.Call(field.Body, stringMethod, searchValue);
    
                //create the final predicate
                Expression<Func<T1, bool>> predicate = Expression.Lambda<Func<T1, bool>>(body, parameter);
                return query.Where(predicate);
            }
    

    【讨论】:

      猜你喜欢
      • 2019-11-27
      • 2011-05-20
      • 1970-01-01
      • 2019-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多