How can i make dynamic expression parameter ?
i was try
public List<Station> GetStationsByTimeAndState(StationState stationState, int? categoryId = null, int? provinceId = null, int? districtId = null)
{
List<Station> stations;
Expression<Func<Station, bool>> exp = p => p.StationState == stationState;
if(categoryId==null){
exp+= p.CategoryId==categoryId;//or exp.Add()//exp.Update()
}
.....
return stations = stationDal.GetList(exp);
}
The GetList method calls the Where query that belongs to Linq in itself.
What I want to do is dynamically constructing and sending the expression inside the Where query.
You need to build a Linq Expression Tree for your example this would look like this:
// Parameter: p
Expression<Func<Station, bool>> exp;
var parameterExpression = Expression.Parameter(typeof(Station));
// p == stationState
var equalsStationState = Expression.Equal(
Expression.Property(stateParameterExpression, nameof(Station.StationState))
Expression.Constant(stationState));
if (categoryId != null)
{
// p.CategoryId == categoryId
var equalsCategory = Expression.Equal(
Expression.Property(stateParameterExpression, nameof(Station.CategoryId)),
Expression.Constant(categoryId));
// (p == stationState) && (p.CategoryId == categoryId)
var andExpression = Expression.AndAlso(equalsStationState, equalsCategory);
// p => (p == stationState) && (p.CategoryId == categoryId)
exp = predicate = Expression.Lambda(
andExpression,
stateParameterExpression);
}
The code above is not complete, but you can get an idea on how it works.
As an alternative you could conditionally chain Where methods, which would be much simpler:
IEnumerable<StationList> stations = allStations
.Where(s => s.StationState == stationState);
if (categoryId != null)
{
stations = stations.Where(s => s.CategoryId == category)
}
return stations;
Related
I have this method bellow to build a dynamic linq query with pagination and sort. But when a send to sort by a property that has relation with other entity like Entity Product has relation with Customer and a call the method to get Customer and in "includeProperties" send Product and i want sort by "Product.Name". it didn't works
The Method:
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "", int page = 0, int pagesize = 100, string sortColumn = "", string sortColumnDir = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (sortColumn != string.Empty && sortColumn.IndexOf(".") <= 0)
{
ParameterExpression[] typeParams = new ParameterExpression[] {
Expression.Parameter(typeof(TEntity), "")
};
System.Reflection.PropertyInfo pi = typeof(TEntity).GetProperty(sortColumn);
sortColumnDir = sortColumnDir == "asc" ? "OrderBy" : "OrderByDescending";
query = (IOrderedQueryable<TEntity>)query.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
sortColumnDir,
new Type[] { typeof(TEntity), pi.PropertyType },
query.Expression,
Expression.Lambda(Expression.Property(typeParams[0], pi), typeParams))
);
}
else if (orderBy != null)
{
query = orderBy(query);
}
else
{
query = query.OrderBy(obj => obj.Desativado);
}
query = query.Skip(page * pagesize).Take(pagesize);
return query.ToList();
}
Anyone can help me?
What does your query builder method do when you pass something like "X.Y" in the sortColumn parameter?
First it tries
if (sortColumn != string.Empty && sortColumn.IndexOf(".") <= 0)
The condition evaluates to false as you have a dot in sortColumn. So this branch is skipped.
Then comes
else if (orderBy != null)
This isn't a hit again, you didn't supply the orderBy but the sortColumn argument.
So finally you end up with
else
{
query = query.OrderBy(obj => obj.Desativado);
}
And this is obviously far from what you wanted to achieve as it won't sort by X.Y at all.
What to do then? You have to options:
You define sorting using callback instead of a path string:
ProductRepository.Get(p => p.Name == "X", includeProperties: "Item", orderBy: query => query.OrderBy(product => product.Item.Codigo))
You change the query builder method so that it accepts a property path in sortColumn. This would need somewhat more effort, though.
Is there possibility to apply searching by any string property of entity? I need to build predicate to use it in LINQ query to database.
var stringPropertyNames = typeof(T)
.GetProperties()
.Where(pi => pi.PropertyType == typeof(string) && pi.GetGetMethod() != null)
.Select(pi => pi.Name);
foreach (var item in stringPropertyNames)
{
// here some code to build Predicate corresponding to sql LIKE statement
}
UPD:
here is working code:
public static IEnumerable<T> ContainsInAnyString<T>(IQueryable<T> collection, string query)
{
var stringPropertyNames = typeof(T)
.GetProperties()
.Where(pi => pi.PropertyType == typeof(string) && pi.GetGetMethod() != null)
.Select(pi => pi.Name);
var item = Expression.Parameter(typeof(T), "item");
var searchArgument = Expression.Constant(query);
var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
foreach (var name in stringPropertyNames)
{
var currentProp = Expression.Property(item, name);
var startsWithDishExpr = Expression.Call(currentProp, method, searchArgument);
var lambda = Expression.Lambda<Func<T, bool>>(startsWithDishExpr, item);
collection = collection.Where(lambda);
}
return collection;
}
Hope it would be helpful for someone.
It really is rather simple:
var entityParameter = Expression.Parameter(typeof(T), "entity");
return
Expression.Lambda<Func<T, bool>>
(
Expression.Equal
(
Expression.MakeMemberAccess(entityParameter, propertyInfo),
Expression.Constant(valueToSearchBy)
),
entityParameter
);
Of course, this does A == B. If you want A like '%' + B + '%' instead, you'll have to change the Expression.Equal to string.Contains. I'll leave that as an excercise, since you haven't shown any of your work yet :)
i wrote function
private Func<CategorizedPosts, bool> CompileExpression(IEnumerable<Category> categories)
{
Expression predicateBody;
ParameterExpression pe = Expression.Parameter(typeof(CategorizedPosts), "post");
Expression left = Expression.Property(pe, typeof(CategorizedPosts).GetProperty("CATEGORY_ID"));
Expression right = Expression.Constant(categories.ElementAt(0).ID);
Expression equal = Expression.Equal(left, right);
predicateBody = equal;
for (int i = 1, j = categories.Count() - 1; i < categories.Count(); ++i )
{
var category = categories.ElementAt(i);
//y => y.CATEGORY_ID == 1 || y.CATEGORY_ID == 2)
left = Expression.Property(pe, typeof(CategorizedPosts).GetProperty("CATEGORY_ID"));
right = Expression.Constant(category.ID);
equal = Expression.Equal(left, right);
predicateBody = Expression.OrElse(predicateBody, equal);
}
var lll = Expression.Lambda<Func<CategorizedPosts, bool>>(predicateBody, pe);
var compiled = lll.Compile();
return compiled;
}
it compiles OK, but when I try to run this query
var ctx = db.Posts.Where(x => true);
if(predicate != null)
{
ctx = ctx.Where(x => x.CategorizedPosts.Where(**predicate**).Count() > 0);
}
IList<Post> posts = ctx.OrderByDescending(x => x.CREATION_DATE).Skip((page - 1) * perPage).Take(perPage).Select(x => new Post
{
POST_ID = x.ID,
TYPE = new Type { ID = x.TYPE_ID, NAME = x.Types.NAME },
AUTHOR = new Author()
{
ID = x.AUTHOR_ID,
NAME = x.Authors.NAME,
},
CATEGORIES = x.CategorizedPosts.Select(y => new Category() { ID = y.CATEGORY_ID, NAME = y.Categories.NAME }),
CREATION_DATE = x.CREATION_DATE,
}).ToList();
EF throws exception about internal error 1025 for Entity Data Provider. How can I perform this query with dynamic where?
You could use the Contains of a collection of Ids (int) and apply it on a where, for sample:
int[] categorieIds = categories.Select(x => x.Id).ToArray();
ctx = ctx.Where(x => x.CategorizedPosts.Any(c => categorieIds .Contains(c.Id));
Some Tips
Remember the Entity Framework works with Expression<Func<T, bool>> in the Where method, not only Func<T, bool>.
You also could try to apply PredicateBuilder class which provides some extensions methods like Or, And, Not, so, you could try this:
var predicate = PredicateBuilder.False<Product>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (p => p.Description.Contains (temp));
}
return dataContext.Products.Where(predicate).ToList();
I would use a .All method in my request, but it seems, it isn't supported.
I have a parameterList, wich contains elements with a value and a name (like a dictionary) and others things.
And Parameters, a list of elements with Value and Name.
All my element in the first list must exist in the second.
The request I would use is :
linq.Where(u => (u.ParametersList.All(param =>
(Parameters.Any(p =>
p.Value== param.Value && p.Name== param.Name)))));
If you have an idea for use something else than the .All, I listen to you :)
I tried
!u.ParametersList.Any(param =>
!(Parameters.Any(p =>
p.Value== param.Value && p.Name== param.Name)));
but I guess Nhibernate don't make differences
I tried also
List<System.Tuple<String, String>> ParamTuples = Parameters.Select(p => new System.Tuple<String, String>(p.Value, p.Name)).ToList();
So, ParamTuples the elements of my second list
linq = linq.Where(url => (url.ParametersList.Any(param =>
ParamTuples.Any(p => p.Item1 == param.Value && p.Item2 == param.Name))));
But it didn't worked neither. Those methods are not supported.
just to give the idea you'll need to count the parameter matches per containing element id and match them with the filterparameter count
class Parameter
{
public virtual Entity Parent { get; set; }
public virtual string Name { get; set; }
public virtual string Value { get; set; }
}
ICriteria filter;
foreach(var param in parameterList)
{
var crit = Expression.And(Expression.Eq("Name", param.Name), Expression.Eq("Value", param.Value);
filter = (filter == null) ? crit : Expression.Or(filter, crit);
}
var subquery = QueryOver.Of<Parameter>()
.Where(filter)
.Select(Projections.Group("Parent.Id"));
.Where(Restrictions.Eq(Projections.Count<Parameter>(p => p.Id), parameterList.Count));
var results = QueryOver.Of<Entity>()
.WuithSubquery.WhereProperty(e => e.Id).IsIn(subquery)
.List();
Update: as from your answer the above matches all but you want any (off the top of my head)
var predicate = PredicateBuilder.False<Parameter>();
foreach (var param in Parameters)
{
predicate = predicate.Or(p => p.Name == param.Name && p.Value == param.Value);
}
// building (u => u.ParametersList.Any(predicate))
var u = Expression.Parameter(typeof(User), "u");
var parametersproperty = Expression.Property(u, "ParametersList");
var anyCall = Expression.Call(parametersproperty, typeof(Queryable).Getmethod("Any"), predicate);
var lambda = Expression.Lambda<User, bool>(u, anyCall);
linq = linq.Where(lambda);
I just want to build a dynamic filters.
And finally to return
Expression<Func<Event, bool>>
I've tried to use the Combine (AndAlso) expressions, but it wasn't workin and finally I found that there are IQueryable queries which works good, but now how can I convert it to the return type -
Expression<Func<Event, bool>>?
My code:
public IQueryable<Event> GetBySearch(EventFilter search)
{
IQueryable<Event> query = this.Context.Events.AsQueryable();
Expression<Func<Event, bool>> expression = null;
if (search.CategoryId != 0)
{
query = query.Where(x => x.CategoryId == search.CategoryId);
}
if (search.SubCategoryId != 0)
{
query = query.Where(x => x.SubCategoryId == search.SubCategoryId);
}
expression = query.Expression as Expression<Func<Event, bool>>; //This convert is not working, it returns null.
return this.Context.Events.Where(expression);
}
Any reason you don't just do the following:
public IQueryable<Event> GetBySearch(EventFilter search)
{
IQueryable<Event> query = this.Context.Events.AsQueryable();
if (search.CategoryId != 0)
{
query = query.Where(x => x.CategoryId == search.CategoryId);
}
if (search.SubCategoryId != 0)
{
query = query.Where(x => x.SubCategoryId == search.SubCategoryId);
}
return query;
}
As Florian said in the comment, returning IQueryables is to be avoided (when possible). The easy solution is to return a list instead:
public List<Event> GetBySearch(EventFilter search)
{
IQueryable<Event> query = this.Context.Events.AsQueryable();
if (search.CategoryId != 0)
{
query = query.Where(x => x.CategoryId == search.CategoryId);
}
if (search.SubCategoryId != 0)
{
query = query.Where(x => x.SubCategoryId == search.SubCategoryId);
}
return query.ToList();
}
This conversion is not valid because Where converts it into a MethodCallExpression
This would be valid:
MethodCallExpression e = query.Expression as MethodCallExpression;