【问题标题】:Creating dynamic expression for entity framework为实体框架创建动态表达式
【发布时间】:2016-05-22 15:50:21
【问题描述】:

我创建了一个通用表达式构建器,它根据条件集合构建谓词。我将谓词传递给存储库中的通用方法。我认为表达式生成器可以正常工作并创建所需的谓词,尽管 Entity Framework 生成的 SQL 脚本与我预期的不同。我已经阅读了许多关于动态查询或 LinqKit 和表达式构建器的问题和文章,其中最相关的是 this comment。如果您能查看我所做的事情并让我知道我是否犯了任何错误,我真的很感激?

这是 ExpressionBuilder 类的代码:

public static class ExpressionBuilder
{
    private static MethodInfo containsMethod = typeof(string).GetMethod("Contains");
    private static MethodInfo startsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
    private static MethodInfo endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) });

    public static Expression<Func<T, bool>> GetExpression<T>(IList<ExpressionModel> filters)
    {
        if (filters == null)
            return null;

        IList<ExpressionModel> nullFreeCollection = filters.OfType<ExpressionModel>().ToList();

        if (nullFreeCollection.Count == 0)
            return null;

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

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

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

                nullFreeCollection.Remove(f1);
                nullFreeCollection.Remove(f2);

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

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

    private static Expression GetExpression<T>(ParameterExpression param, ExpressionModel filter)
    {
        MemberExpression member = Expression.Property(param, filter.PropertyName);
        ConstantExpression constant = Expression.Constant(filter.Value);

        switch (filter.Operator)
        {
            case ExpressionOperators.Equals:
                return Expression.Equal(member, constant);
            case ExpressionOperators.GreaterThan:
                return Expression.GreaterThan(member, constant);
            case ExpressionOperators.LessThan:
                return Expression.LessThan(member, constant);
            case ExpressionOperators.GreaterThanOrEqual:
                return Expression.GreaterThanOrEqual(member, constant);
            case ExpressionOperators.LessThanOrEqual:
                return Expression.LessThanOrEqual(member, constant);
            case ExpressionOperators.Contains:
                return Expression.Call(member, containsMethod, constant);
            case ExpressionOperators.StartsWith:
                return Expression.Call(member, startsWithMethod, constant);
            case ExpressionOperators.EndsWith:
                return Expression.Call(member, endsWithMethod, constant);
        }

        return null;
    }

    private static BinaryExpression GetExpression<T>(ParameterExpression param, ExpressionModel filter1, ExpressionModel filter2)
    {
        Expression bin1 = GetExpression<T>(param, filter1);
        Expression bin2 = GetExpression<T>(param, filter2);

        return Expression.AndAlso(bin1, bin2);
    }

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

这是通用存储库方法:

    public IEnumerable<TEntity> RetrieveCollectionAsync(Expression<Func<TEntity, bool>> predicate)
    {
        try
        {
            return DataContext.Set<TEntity>().Where(predicate);
        }
        catch (Exception ex)
        {
            Logger.Error(ex);
            throw ex;
        }
    }

并由 Entity Framework for Sql 生成脚本(我期望一个带有几个 where 子句的选择查询):

SELECT 
    CAST(NULL AS uniqueidentifier) AS [C1], 
    CAST(NULL AS uniqueidentifier) AS [C2], 
    CAST(NULL AS varchar(1)) AS [C3], 
    CAST(NULL AS uniqueidentifier) AS [C4], 
    CAST(NULL AS uniqueidentifier) AS [C5], 
    CAST(NULL AS uniqueidentifier) AS [C6], 
    CAST(NULL AS datetime2) AS [C7], 
    CAST(NULL AS datetime2) AS [C8], 
    CAST(NULL AS varchar(1)) AS [C9], 
    CAST(NULL AS uniqueidentifier) AS [C10], 
    CAST(NULL AS varchar(1)) AS [C11], 
    CAST(NULL AS uniqueidentifier) AS [C12], 
    CAST(NULL AS uniqueidentifier) AS [C13], 
    CAST(NULL AS uniqueidentifier) AS [C14], 
    CAST(NULL AS uniqueidentifier) AS [C15], 
    CAST(NULL AS datetime2) AS [C16], 
    CAST(NULL AS varchar(1)) AS [C17], 
    CAST(NULL AS datetime2) AS [C18], 
    CAST(NULL AS varchar(1)) AS [C19], 
    CAST(NULL AS tinyint) AS [C20]
    FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
    WHERE 1 = 0

我正在使用

  • EntityFramework 6.0
  • .Net Framework 4.6
  • ASP.NET MVC 5

更新表达模型:

public class ExpressionModel
{
    public string PropertyName { get; set; }
    public ExpressionOperators Operator { get; set; }
    public object Value { get; set; }
}

另一个缺失的部分是一个通用映射器,它将给定的搜索条件映射到一个新的 ExpressionModel,我认为它与这个问题无关。

【问题讨论】:

  • 看起来过于复杂。您能否包括缺少的部分 - ExpressionModel 类和您从发布的方法内部调用的其他 GetExpression 函数。只看代码就很不清楚为什么你有一个带有 2 个模型参数的函数。
  • @IvanStoev 我已根据要求更新了问题。
  • @IvanStoev this 文章也提出了类似的方法。

标签: c# entity-framework expression-trees dynamic-linq


【解决方案1】:

正如我在 cmets 中提到的,实现过于复杂。

一、这个方法

private static BinaryExpression GetExpression<T>(ParameterExpression param, ExpressionModel filter1, ExpressionModel filter2)

检查过滤器计数、删除已处理项目等的整个逻辑是多余的。 AND 条件可以像这样轻松链接

((Condition1 AND Condition2) AND Condition3) AND Condition4 ...

所以只需删除该功能。

二、这个函数

private static Expression GetExpression<T>(ParameterExpression param, ExpressionModel filter)

名字不好,不需要通用的T,因为它没有在内部使用。

相反,将签名更改为

private static Expression MakePredicate(ParameterExpression item, ExpressionModel filter)
{
    // implementation (same as posted)
}

最后,public方法就这么简单:

public static Expression<Func<T, bool>> MakePredicate<T>(IEnumerable<ExpressionModel> filters)
{
    if (filters == null) return null;
    filters = filters.Where(filter => filter != null);
    if (!filters.Any()) return null;
    var item = Expression.Parameter(typeof(T), "item");
    var body = filters.Select(filter => MakePredicate(item, filter)).Aggregate(Expression.AndAlso);
    var predicate = Expression.Lambda<Func<T, bool>>(body, item);
    return predicate;
}

附:并且不要忘记做null检查使用情况:

// should not be called Async
public IEnumerable<TEntity> RetrieveCollectionAsync(Expression<Func<TEntity, bool>> predicate)
{
    try
    {
        var query = DataContext.Set<TEntity>().AsQueryable();
        if (predicate != null)
            query = query.Where(predicate);
        return query;
    }
    catch (Exception ex)
    {
        Logger.Error(ex);
        throw ex; // should be: throw;
    }
}

【讨论】:

  • 感谢您的回答伊万。如果我在存储库中没有弄错,您正在检索整个表并使用 Linq To Object 您通过谓词运行过滤器。我相信您忽略了我提到的部分,我期望实体框架生成一个带有一些 where 子句的脚本。我正在寻找 Linq to Entity 解决方案,而不是 Linq to Object。
  • @DeveloperX 仔细阅读答案。它使用 LINQ to Object only 来轻松构建表达式。但表达式是 EF 兼容的。基本上它是您的代码的简化实现。公共MakePredicate 方法对应于您的公共GetExpression。如果你愿意,你可以用你自己的方式来称呼它。
  • 如果你的意思是var query = DataContext.Set&lt;TEntity&gt;().AsQueryable();这一行,它不会执行查询。检查null 参数只是一个好习惯。甚至您的表达式生成器方法也有返回 null 的分支。
  • 在这一行中非常巧妙地使用了 Linq filters.Select(filter =&gt; MakePredicate(item, filter)).Aggregate(Expression.AndAlso); +1 :)
  • @DeveloperX 嗨。如果您还没有揭开它的神秘面纱:) 最近我发现 EF 会生成一个假 SQL 查询,就像您在 Where 子句是 constant false 时看到的那样 - 请参阅 Entity Framework mysterious error .检查您传递给函数的条件,并确保它们中的任何一个都不会不断地评估 false(这将导致整个过滤器成为常量 false,因为 And 组合器)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-29
  • 2011-07-15
相关资源
最近更新 更多