2013-04-12 42 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; 
     }