I have a existing lambda-expression which was created like:
Expression<Func<Entities.Area, bool>> where = (x => (x.Created > this.Value || (x.Changed != null && x.Changed > this.Value)));
Now, I have to extend this expression with this one:
Expression<Func<Entities.Area, bool>> whereAdd = (x => x.Client.Id == ClientInfo.CurrentClient.Id);
The Result should be like:
Expression<Func<Entities.Area, bool>> where = (x => (x.Created > this.Value || (x.Changed != null && x.Changed > this.Value)) && x.Client.Id == ClientInfo.CurrentClient.Id);
I cannot change the creation of the first expression directly because it is not my code.
I hope someone can help me how to extend the first lambda-expression.
Just create a new AndAlso expression taking the bodies of both your expressions and make a new lambda expression out of that:
ParameterExpression param = Expression.Parameter(typeof(Entities.Area), "x");
Expression body = Expression.AndAlso(where.Body, whereAdd.Body);
var newWhere = Expression.Lambda<Func<Entities.Area, bool>>(body, param);
Console.WriteLine(newWhere.ToString());
// x => (((x.Created > Convert(value(UserQuery).Value)) OrElse ((x.Changed != null) AndAlso (x.Changed > Convert(value(UserQuery).Value)))) AndAlso (x.Client.Id == ClientInfo.CurrentClient.Id))
Combining two expressions (Expression<Func<T, bool>>)
var body = Expression.AndAlso(where.Body, whereadd.Body);
var lambda = Expression.Lambda<Func<Entities.Area,bool>>(body, where.Parameters[0]);
Related
I have a list of strings string[] searchValues and a LINQ expression
queryable.Where(c => c.Tags != null && searchValues.All(s => c.Tags.Contains(s)));
where .Tags is a List<string>
I want to rewrite it as an expression (filters and sorting orders are coming from UI as strings, and I wanted to create a generic method to convert it to expressions)
So far I've written this, but it's incorrect.
private static Expression GetFilterExpressionIListString2(MemberExpression memberExp, string[] values)
{
var anyMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Any" && m.GetParameters().Length == 2);
var specificAnyMethod = anyMethod.MakeGenericMethod(typeof(string));
Expression<Func<List<string>, bool>> lambda = s => values.All(v => s.Contains(v));
var anyExp = Expression.Call(specificAnyMethod, memberExp, lambda);
var notNullExp = Expression.NotEqual(memberExp, Expression.Constant(null));
var andExp = Expression.AndAlso(notNullExp, anyExp);
return andExp;
}
I'm writing a query to retrieve some data from a DynamicDataStore in Episerver. When I run the code I get the following error:
System.Data.SqlClient.SqlException: Incorrect syntax near '>'.
Here's the relevant query:
BlogContentStore store = new BlogContentStore();
IQueryable<UwlBlogPost> posts = store.Posts.Where(p => Blog == p.BlogId && p.ReadyToPost && p.PostOn <= DateTime.Now);
if (taggedPeople.Count() > 0 || taggedDepartments.Count() > 0 || taggedDepartments.Count() > 0)
{
posts = posts.Where(p => p.PeopleTags.Intersect(taggedPeople).Count() > 0
|| p.DepartmentTags.Intersect(taggedDepartments).Count() > 0
|| p.KeywordTags.Intersect(taggedKeywords).Count() > 0);
}
posts = posts.OrderByDescending(p => p.PostOn).Take(DisplayCount);
The syntax for everything looks alright to me, and it compiles okay.
I managed to solve the issue. Looking at the SQL server log I was able to see that the Intersect statements were not getting turned into SQL, so I decided to dynamicly create the query expression to get around it:
if (taggedPeople.Count > 0 || taggedDepartments.Count > 0 || taggedKeywords.Count > 0)
{
ParameterExpression paramExpr = Expression.Parameter(typeof(UwlBlogPost), "p");
Expression peopleTagsExpr = Expression.Property(paramExpr, "PeopleTags");
Expression deptTagsExpr = Expression.Property(paramExpr, "DepartmentTags");
Expression keywordTagsExpr = Expression.Property(paramExpr, "KeywordTags");
Expression filterExpr = null;
if(taggedPeople.Count > 0)
{
filterExpr = FilterLambda<string>(taggedPeople, peopleTagsExpr, paramExpr);
}
if(taggedDepartments.Count > 0)
{
Expression filter = FilterLambda<int>(taggedDepartments, deptTagsExpr, paramExpr);
filterExpr = (filterExpr == null) ? filter : MatchAll ? Expression.And(filterExpr, filter) : Expression.Or(filterExpr, filter);
}
if(taggedKeywords.Count > 0)
{
Expression filter = FilterLambda<int>(taggedKeywords, keywordTagsExpr, paramExpr);
filterExpr = (filterExpr == null) ? filter : MatchAll ? Expression.And(filterExpr, filter) : Expression.Or(filterExpr, filter);
}
posts = posts.Where(Expression.Lambda<Func<UwlBlogPost, bool>>(filterExpr, new[] { paramExpr }));
}
private Expression FilterLambda<T>(List<T> tags, Expression field, ParameterExpression paramExpr)
{
Expression firstTag = Expression.Constant(tags.First());
Expression root = Expression.Call(field, tags.GetType().GetMethod("Contains"), firstTag);
if (tags.Count > 1)
{
foreach (T tag in tags.Skip(1))
{
Expression singleTag = Expression.Constant(tag);
Expression cond = Expression.Call(field, tags.GetType().GetMethod("Contains"), singleTag);
root = MatchAll ? Expression.And(root, cond) : Expression.Or(root, cond);
}
}
return root;
}
I have a list of string retreived this way :
List<string> keyWords = db.MotCleRecherche.Select(t => t.MotClé).ToList();
I also have a query that takes many parameters to be executed :
object = db.DAapp.Where(t => t.CODE_ART.StartsWith(s) && t.DATE_CREAT >= debut && t.DATE_CREAT < fin).ToList()
now... I want to add this kind of condition :
db.DAapp.Where(t => t.DESC_ART.ToLower().Contains(keywords.ToLower()))
or
db.DAapp.Where(t => t.DESC_ART.ToLower().Intersect(keywords.ToLower()))
I guess you could see it comming... I can't figure how to really make this work... all i know is considering a list X filed and Y list filled:
X.Intersect(Y).Any()
will return true if there is something equal... but DESC_ART is just ONE long string and i want to know if some of my keywords are in there
I agree with Stephen that you should cast the keyWords to lower first before comparing. But if you really need to do this with linq you can do something like this.
var result = db.DAapp.Where(t => keywords.Any(keyword=> string.Equals(keyword,t.DESC_ART, StringComparison.InvariantCultureIgnoreCase )));
This will cause a to lower to get called on each string every iteration of your linq loop so its expensive.
First add this to your project (for example to your controller):
static Expression<Func<T, bool>> AnyOf<T>(
params Expression<Func<T, bool>>[] expressions)
{
if (expressions == null || expressions.Length == 0) return x => false;
if (expressions.Length == 1) return expressions[0];
var body = expressions[0].Body;
var param = expressions[0].Parameters.Single();
for (int i = 1; i < expressions.Length; i++)
{
var expr = expressions[i];
var swappedParam = new SwapVisitor(expr.Parameters.Single(), param)
.Visit(expr.Body);
body = Expression.OrElse(body, swappedParam);
}
return Expression.Lambda<Func<T, bool>>(body, param);
}
class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
I find this from stackoverflow. now you can create desired query as below :
var filters = new List<Expression<Func<Models.DAapp, bool>>>();
foreach (var st in keyWords)
filters.Add(d => d.DESC_ART.ToLower().Contains(st.ToLower()));
var lambda = AnyOf(filters.ToArray());
var q = db.DAapp.Where(t =>
t.CODE_ART.StartsWith(s)
&& t.DATE_CREAT >= debut
&& t.DATE_CREAT < fin
);
q = q.Where(lambda);
var res = q.ToList();
Please be noticed that, this solution creates only one select query with multiple where expressions. which is more efficient that other solutions like below that contains multiple select queries inside where clause :
var q = db.DAapp.Where(t =>
t.CODE_ART.StartsWith(s)
&& t.DATE_CREAT >= debut
&& t.DATE_CREAT < fin
&& keyWords.Any(k => t.DESC_ART.ToLower().Contains(k.ToLower()))
);
I have a question, how do I add another filter and this I have to validate that was selected?
private Expression < Func < Entity.Modelos.Flux, bool >> Filter() {
var dateStart = dtpDateStart.Value.Date;
var dateEnd = dtpDateEnd.Value.Date;
Expression < Func < Entity.Modelos.Flux, bool >> expr = null;
expr = f = > f.DatFlux >= dateStart.Date && f.DatFlux <= dateEnd.Date;
if (txtDescription.Text != String.Empty) {
//add filter
}
return expr;
}
Update: I'll use the expression in this function:
public virtual IQueryable < T > Filter(Expression < Func < T, bool >> expressao) {
return DbSet.Where(expressao).AsQueryable < T > ();
}
What I was trying to do is this but with an Expression
public List < Users > GetUsers(int ? id, string name) {
using(DBContext ctx = new DBContext()) {
IQueryable query = ctx.Usuarios;
if (id.HasValue)
query = query.Where(x = > x.ID == id);
if (!string.IsNullOrEmpty(name))
query = query.Where(x = > x.Name.StartsWith(name));
return query.ToList();
}
}
inherit from ExpressionVisitor
public class MyVisitor: ExpressionVisitor
{
private LambdaExpression visitor;
public Expression Modify(Expression expression, LambdaExpression visitor)
{
this.visitor = visitor;
return Visit(expression);
}
protected override Expression VisitBinary(BinaryExpression b)
{
var binary = visitor.Body as BinaryExpression;
return Expression.MakeBinary(ExpressionType.AndAlso, b, binary, b.IsLiftedToNull, b.Method);
}
}
your Filter() method might look like this
private Expression<Func<Entity.Modelos.Flux, bool>> Filter()
{
var dateStart = dtpDateStart.Value.Date;
var dateEnd = dtpDateEnd.Value.Date;
var description = txtDescription.Text;
Expression<Func<Entity.Modelos.Flux, bool>> expr = null;
expr = f => f.DatFlux >= dateStart.Date && f.DatFlux <= dateEnd.Date;
if (description != String.Empty)
{
//add filter
Expression<Func<Entity.Modelos.Flux, bool>> other = f => f.Description == description;
var modifier = new MyVisitor();
expr = (Expression<Func<Entity.Modelos.Flux, bool>>)modifier.Modify((Expression)expr, (LambdaExpression)other);
}
return expr;
}
example
have a look at the following for more information
How to: Modify Expression Trees (C# and Visual Basic)
If this is just a demo of your requirements, then you can create and modify Expression Trees using the System.Linq.Expressions namespace.
However, in the case of your question, it would probably be easier to use EF:
bool filterDescription = !String.IsNullOrEmpty( txtDescription.Text );
expr = f =>
(
( f.DatFlux >= dateStart.Date && f.DatFlux <= dateEnd.Date )
&&
( !filterDescription || ... add filter ... )
)
;
or normal C#:
if ( String.IsNullOrEmpty( txtDescription.Text ) )
{
expr = f => f.DatFlux >= dateStart.Date && f.DatFlux <= dateEnd.Date;
}
else
{
expr = f => f.DatFlux >= dateStart.Date && f.DatFlux <= dateEnd.Date
&&
... add filter ...
;
}
Okay, well here is a sample on how to embed lambda statements. It's not your example, but here ya go:
Func<int, int, EventHandler> makeHandler =
(dx, dy) => (sender, e) => {
var btn = (Button) sender;
btn.Top += dy;
btn.Left += dx;
};
btnUp.Click += makeHandler(0, -1);
btnDown.Click += makeHandler(0, 1);
btnLeft.Click += makeHandler(-1, 0);
btnRight.Click += makeHandler(1, 0);
It is simple.Visit this link
the tricky thing is invoking the OrderByAlias - using MakeGenericMethod may be the way, as shown in the link above.
Try this way:
private Expression<Func<Entity.Modelos.Flux, bool>> Filter()
{
var dateStart = dtpDateStart.Value.Date;
var dateEnd = dtpDateEnd.Value.Date;
Func<Entity.Modelos.Flux, bool> expr = null;
expr = f => f.DatFlux >= dateStart.Date && f.DatFlux <= dateEnd.Date;
if(txtDescription.Text != String.Empty)
{
expr = f => expr(f) && f.Title.Equals(txtDescription.Text); // ← Your additional filter
}
return f => expr(f);
}
The following Linq-to-Entities query works fine:
var query = repository.Where(r => r.YearProp1.HasValue &&
r.YearProp1 >= minYear &&
r.YearProp1 <= maxYear);
My database has a dozen or so columns that all report year-related information (short? data type). I want to reuse the same Linq-to-Entities logic for all these columns. Something like:
Func<RepoEntity, short?> fx = GetYearPropertyFunction();
var query = repository.Where(r => fx(r).HasValue &&
fx(r) >= minYear &&
fx(r) <= maxYear);
This results in the error:
LINQ to Entities does not recognize the method
'System.Nullable`1[System.Int16] fx(RepoEntity)' method, and this
method cannot be translated into a store expression.
I understand why I am getting the error, but am wondering if there is a workaround that doesn't involve duplicating code a dozen times just to change the property on which the SQL query is operating.
I would be reusing the function in more than one query, so I guess the general version of my question is: Is there a way to convert a simple property-getter lambda function to an Expression that can be consumed by Linq-to-Entities?
Building off of Raphaël Althaus' answer, but adding the generic selector you were originally looking for:
public static class Examples
{
public static Expression<Func<MyEntity, short?>> SelectPropertyOne()
{
return x => x.PropertyOne;
}
public static Expression<Func<MyEntity, short?>> SelectPropertyTwo()
{
return x => x.PropertyTwo;
}
public static Expression<Func<TEntity, bool>> BetweenNullable<TEntity, TNull>(Expression<Func<TEntity, Nullable<TNull>>> selector, Nullable<TNull> minRange, Nullable<TNull> maxRange) where TNull : struct
{
var param = Expression.Parameter(typeof(TEntity), "entity");
var member = Expression.Invoke(selector, param);
Expression hasValue = Expression.Property(member, "HasValue");
Expression greaterThanMinRange = Expression.GreaterThanOrEqual(member,
Expression.Convert(Expression.Constant(minRange), typeof(Nullable<TNull>)));
Expression lessThanMaxRange = Expression.LessThanOrEqual(member,
Expression.Convert(Expression.Constant(maxRange), typeof(Nullable<TNull>)));
Expression body = Expression.AndAlso(hasValue,
Expression.AndAlso(greaterThanMinRange, lessThanMaxRange));
return Expression.Lambda<Func<TEntity, bool>>(body, param);
}
}
Could be used somewhat like the original query you were looking for:
Expression<Func<MyEntity, short?>> whatToSelect = Examples.SelectPropertyOne;
var query = Context
.MyEntities
.Where(Examples.BetweenNullable<MyEntity, short>(whatToSelect, 0, 30));
A predicate is a filter in itself that should evaluate to bool (for whether or not to include it in the results). You can rework your method to look like this and it should work:
public static Expression<Func<RepoEntity, bool>> FitsWithinRange(int minYear, int maxYear)
{
return w => w.HasValue && w >= minYear && w <= maxYear;
}
Edit: Oh and to use it:
var query = repository.Where(Repository.FitsWithinRange(minYear, maxYear));
You could do something like that (not sure if it will work "as is" in linq2 entities, but if you have a problem... just tell)
usage
var query = <your IQueryable<T> entity>.NullableShortBetween(1, 3).ToList();
function
public static IQueryable<T> NullableShortBetween<T>(this IQueryable<T> queryable, short? minValue, short? maxValue) where T: class
{
//item (= left part of the lambda)
var parameterExpression = Expression.Parameter(typeof (T), "item");
//retrieve all nullable short properties of your entity, to change if you have other criterias to get these "year" properties
var shortProperties = typeof (T).GetProperties().Where(m => m.CanRead && m.CanWrite && m.PropertyType == typeof(short?));
foreach (var shortProperty in shortProperties)
{
//item (right part of the lambda)
Expression memberExpression = parameterExpression;
//item.<PropertyName>
memberExpression = Expression.Property(memberExpression, shortProperty);
//item.<PropertyName>.HasValue
Expression firstPart = Expression.Property(memberExpression, "HasValue");
//item.<PropertyName> >= minValue
Expression secondPart = Expression.GreaterThanOrEqual(memberExpression, Expression.Convert(Expression.Constant(minValue), typeof (short?)));
//item.<PropertyName> <= maxValue
var thirdPart = Expression.LessThanOrEqual(memberExpression, Expression.Convert(Expression.Constant(maxValue), typeof (short?)));
//item.<PropertyName>.HasValue && item.<PropertyName> >= minValue
var result = Expression.And(firstPart, secondPart);
//item.<PropertyName>.HasValue && item.<PropertyName> >= minValue && item.<PropertyName> <= maxValue
result = Expression.AndAlso(result, thirdPart);
//pass the predicate to the queryable
queryable = queryable.Where(Expression.Lambda<Func<T, bool>>(result, new[] {parameterExpression}));
}
return queryable;
}
EDIT : another solution, based on "simple" reflection, which "looks" as the one you want
public static short? GetYearValue<T>(this T instance)
{
var propertyInfo = typeof(T).GetProperties().FirstOrDefault(m => m.CanRead && m.CanWrite && m.PropertyType == typeof(short?));
return propertyInfo.GetValue(instance, null) as short?;
}
usage
var result = list.Where(item => item.GetYearValue() != null && item.GetYearValue() >= 1 && item.GetYearValue() <= 3).ToList();