2016-02-11 59 views
1

我已經創建了一個基於條件集合構建謂詞的通用表達式構建器。我將謂詞傳遞給存儲庫中的通用方法。我認爲表達式生成器工作正常,並創建所需的謂詞,儘管實體框架生成的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; 
     } 
    } 

而生成的腳本由實體框架的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框架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,我相信它與這個問題無關。

+1

看起來過於複雜。你能否包含缺少的部分 - ExpressionModel類和其他'GetExpression'函數,你從發佈的方法中調用。僅僅看代碼就不清楚爲什麼你有一個帶有2個模型參數的函數。 –

+0

@IvanStoev我已根據要求更新了該問題。 – DeveloperX

+0

@IvanStoev [this](https://msdn.microsoft.com/en-us/library/bb882637.aspx)文章也提出了類似的方法。 – DeveloperX

回答

3

正如我在評論中提到的那樣,實現過於複雜。

首先,該方法

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 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; 
    } 
} 
+0

謝謝你的回答伊萬。如果我沒有錯誤地在版本庫中檢索整個表格,並使用Linq To Object,則可以通過謂詞運行過濾器。我相信你忽略了我提到的部分,我期待實體框架用幾個where子句生成一個腳本。我正在尋找Linq to Entity解決方案而不是Linq to Object。 – DeveloperX

+0

@DeveloperX仔細閱讀答案。它只使用LINQ來對象**來輕鬆構建表達式。但表達式與EF兼容。基本上它簡化了你的代碼的實現。公共'MakePredicate'方法對應於你的公共'GetExpression'。如果你喜歡,你可以稱它爲你的方式。 –

+0

如果你的意思是這行'var query = DataContext.Set ().AsQueryable();',它不會執行查詢。檢查'null'參數只是一個好習慣。即使你的表達式生成器方法有分支返回'null'。 –