Question

I have got an MVC application that uses EF 5 Code First for Db operations. Also there is Generic Repository pattern on EF.

Before sending query to DB layer i make some operations such as creating dynamic linq query and combine it with another query.

 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]);

My Get method at DB layer is like below.

 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);
            }
        }

As you can see there is a filter parameter with Expression<Func<TEntity, bool>> type. So if i write manual filters it works very well. But i want to step forward and create dynamic filters search keyword on all properties of given model. For this purpose i use method below.

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

Method above eliminate virtual members and members whose name contains "ID" suffix and generate a dynamic expression sampled below.

.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
}

But when i try to run application i got error below.

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

I removed ToString() methods and simplified query like below.

.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
}

But this time i got an error tells me that c is not bound to Linq.

Do you have any idea how can i create dynamic query successfully?

Était-ce utile?

La solution

Finally i could write a method that create LINQ query for given entity and search string. This method use properties whose data type is string.

Also a IEnumerable parameter lets you to specify properties that will be included in LINQ query.

 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;
        } 
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top