使用动态Linq查询过滤数据库

时间:2013-04-12 07:48:45

标签: linq linq-to-sql entity-framework-5 linq-expressions

我有一个MVC应用程序,它使用EF 5 Code First进行Db操作。 EF上还有Generic Repository模式。

在向DB层发送查询之前,我会进行一些操作,例如创建动态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]);

我在DB层的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。

您是否知道如何成功创建动态查询?

1 个答案:

答案 0 :(得分: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;
        }