2013-04-12 44 views
1

我有一個MVC應用程序,它使用EF 5 Code First作爲Db操作。 EF上還有Generic Repository模式。使用動態Linq查詢過濾數據庫

在發送查詢到數據庫層之前,我做了一些操作,比如創建動態LINQ查詢並將它與另一個查詢結合起來。

Expression<Func<AssetItemInfo, bool>> dynamicFilter = DynamicLinqFactory<AssetItemInfo>.GetFilter(cmd.sSearch, searchColumns); 
      Expression<Func<AssetItemInfo, bool>> deleteFilter = c => c.CurrentStatus != AssetStatus.Deleted;    
      var body = Expression.AndAlso(dynamicFilter.Body, deleteFilter.Body); 
      Expression<Func<AssetItemInfo, bool>> filter = Expression.Lambda<Func<AssetItemInfo, bool>>(body, dynamicFilter.Parameters[0]); 

我在數據庫層的Get方法如下所示。

public virtual PaginatedList<TEntity> Get(
      Expression<Func<TEntity, bool>> filter = null, 
      Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, 
      string includeProperties = "", 
      int? page = null, 
      int? take = null 
     ) { 
      IQueryable<TEntity> query = dbSet; 

      if(filter != null) { 
       query = query.Where(filter); 
      } 

      if(includeProperties != null) { 
       foreach(var includeProperty in includeProperties.Split 
        (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { 
        query = query.Include(includeProperty); 
       } 
      } 
      if(orderBy != null) { 
       return orderBy(query).ToList().ToPaginatedList(page.Value, take.Value); 
      } 
      else { 
       if(!page.HasValue) page = 0; 
       if(!take.HasValue) take = 0; 
       return query.ToList().ToPaginatedList(page.Value, take.Value); 
      } 
     } 

正如您所見,有一個Expression<Func<TEntity, bool>>類型的過濾器參數。所以如果我編寫手動過濾器,它工作得很好。 但我想前進一步並在給定模型的所有屬性上創建動態過濾器搜索關鍵字。 爲此,我使用下面的方法。上述

public class DynamicLinqFactory<TEntity> where TEntity : class, IDomainEntity { 
     public static Expression<Func<TEntity, bool>> GetFilter(string filter, IEnumerable<string> filterTargets = null) { 
      ParameterExpression c = Expression.Parameter(typeof(TEntity), "c"); 
      Type[] ContainsTypes = new Type[1]; 
      ContainsTypes[0] = typeof(string); 
      MethodInfo myContainsInfo = typeof(string).GetMethod("Contains", ContainsTypes); 
      if(filterTargets == null) { 
       filterTargets = typeof(TEntity).GetProperties().Where(p => !p.GetMethod.IsVirtual && !p.Name.EndsWith("ID")).Select(p=>p.Name).ToList(); 
      } 
      List<Expression> myFilterExpressions = 
      filterTargets.Select<string, Expression>(s => 
       Expression.Call(
       Expression.Call(
        Expression.Property(c, typeof(TEntity).GetProperty(s)), 
        "ToString", 
        null, 
        null 
       ), 
       myContainsInfo, 
       Expression.Constant(filter) 
      ) 
      ).ToList(); 

      Expression OrExpression = null; 
      foreach(Expression myFilterExpression in myFilterExpressions) { 
       if(OrExpression == null) { 
        OrExpression = myFilterExpression; 
       } 
       else { 
        OrExpression = Expression.Or(myFilterExpression, OrExpression); 
       } 
      } 
      Expression<Func<TEntity, bool>> predicate = Expression.Lambda<Func<TEntity, bool>>(OrExpression, c); 
      return predicate; 
     } 
    } 

方法消除虛擬成員和成員名稱中包含「ID」和後綴生成動態表達式下面採樣。

.Lambda #Lambda1<System.Func`2[Radore.Models.Asset.AssetItem,System.Boolean]>(Radore.Models.Asset.AssetItem $c) { 
    .Call (.Call ($c.UpdateDate).ToString() 
    ).Contains("tes") | .Call (.Call ($c.UpdatedBy).ToString()).Contains("tes") | .Call (.Call ($c.ServiceTag).ToString()).Contains("tes") 
    | .Call (.Call ($c.Price).ToString()).Contains("tes") | .Call (.Call ($c.CurrentStatus).ToString()).Contains("tes") | .Call (.Call ($c.CreatedDate).ToString() 
    ).Contains("tes") | .Call (.Call ($c.CreatedBy).ToString()).Contains("tes") | .Call (.Call ($c.Name).ToString()).Contains("tes") 
    && (System.Int32)$x.CurrentStatus != 3 
} 

但是,當我嘗試運行應用程序,我得到了下面的錯誤。

LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression. 

我刪除了ToString()方法和下面的簡化查詢。

.Lambda #Lambda1<System.Func`2[Radore.Models.Asset.AssetItem,System.Boolean]>(Radore.Models.Asset.AssetItem $c) { 
    .Call (.Call ($c.Name).ToString()).Contains("tes") && (System.Int32)$c.CurrentStatus != 3 
} 

但是這一次我得到一個錯誤告訴我c沒有綁定Linq。

你知道如何成功創建動態查詢嗎?

回答

0

最後,我可以寫一個方法,爲給定的實體和搜索字符串創建LINQ查詢。 此方法使用數據類型爲字符串的屬性。

此外,IEnumerable參數允許您指定將包含在LINQ查詢中的屬性。

public static Expression<Func<TEntity, bool>> GetFilter(string filter, IEnumerable<string> filterTargets = null) { 
      ParameterExpression c = Expression.Parameter(typeof(TEntity), "c"); 
      Type[] ContainsTypes = new Type[1]; 
      ContainsTypes[0] = typeof(string); 
      MethodInfo myContainsInfo = typeof(string).GetMethod("Contains", ContainsTypes); 
      if(filterTargets == null) { 
       filterTargets = typeof(TEntity).GetProperties().Where(p => p.PropertyType == typeof(string)).Select(p=>p.Name).ToList(); 
      } 
      List<Expression> myFilterExpressions = 
      filterTargets.Select<string, Expression>(s => 
       Expression.Call(
       Expression.Property(c, typeof(TEntity).GetProperty(s)),     
       myContainsInfo, 
       Expression.Constant(filter) 
      ) 
      ).ToList(); 

      Expression OrExpression = null; 
      foreach(Expression myFilterExpression in myFilterExpressions) { 
       if(OrExpression == null) { 
        OrExpression = myFilterExpression; 
       } 
       else { 
        OrExpression = Expression.Or(myFilterExpression, OrExpression); 
       } 
      } 
      Expression<Func<TEntity, bool>> predicate = Expression.Lambda<Func<TEntity, bool>>(OrExpression, c); 
      return predicate; 
     }