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);
}
Related
I can't make where clauses in dynamic query use OR instead of AND
The outcome query string is:
DECLARE #__dateStart_0 datetime2 = '2021-03-15T00:00:00.0000000-03:00';
DECLARE #__dateEnd_1 datetime2 = '2021-03-17T00:00:00.0000000';
SELECT
[f].[Id], [f].[DateCreated], [f].[Description], [f].[ExperienceLevel],
[f].[PostType], [f].[SocialInteractionsId], [f].[Title], [f].[UserId],
[q].[ErrorCode], [q].[ErrorName], [q].[RepositoryLink]
FROM
[ForumPost] AS [f]
INNER JOIN
[QuestionsForumPost] AS [q] ON [f].[Id] = [q].[Id]
INNER JOIN
[SocialInteractions] AS [s] ON [f].[SocialInteractionsId] = [s].[Id]
WHERE
(CONVERT(date, [f].[DateCreated]) >= #__dateStart_0)
AND (CONVERT(date, [f].[DateCreated]) <= #__dateEnd_1)
ORDER BY
[s].[CalculatedInteractions] DESC
Expected query statement:
In the WHERE clause, there should be an OR
public async Task<IEnumerable<QuestionsForumPost>> GetQuestionByCriteriaPaged(
ForumQuestionCriteria forumQuestionCriteria,
PaginationFilter paginationFilter)
{
var query = this._forumContext
.QuestionsForumPost
.AsQueryable();
if (!(forumQuestionCriteria.StartDate is null) && !(forumQuestionCriteria.EndDate is null))
{
var dateStart = forumQuestionCriteria.StartDate?.Date;
var dateEnd = forumQuestionCriteria.EndDate?.Date;
query = query.Where(qf => qf.DateCreated.Date >= dateStart && qf.DateCreated.Date <= dateEnd);
}
if (!String.IsNullOrEmpty(forumQuestionCriteria.Title))
{
var wordsArrFromTitle = forumQuestionCriteria.Title.Split(' ');
query = query
.Where(qf =>
qf.Title.Split(' ', StringSplitOptions.None)
.Any(x => wordsArrFromTitle.Contains(x)));
}
if (!String.IsNullOrEmpty(forumQuestionCriteria.ErrorCode))
query = query.Where(qf => qf.ErrorCode.Contains(forumQuestionCriteria.ErrorCode));
var result = await query
.Include(qf => qf.SocialInteractions)
.Skip((paginationFilter.PageNumber - 1) * paginationFilter.PageSize)
.Take(paginationFilter.PageSize)
.ToListAsync();
return result;
}
I Got it working by installing linqKit and using predicateBuilder. Also, expression trees does not translate some linq queries to sql at runtime, so an expression is needed in order to construct a new predicate with the desired linq query
public async Task<IEnumerable<QuestionsForumPost>> GetQuestionByCriteriaPaged(
ForumQuestionCriteria forumQuestionCriteria,
PaginationFilter paginationFilter)
{
var criteriaKeyWords = "";
var query = this._forumContext
.QuestionsForumPost
.AsQueryable();
var dateQuery = PredicateBuilder.New<QuestionsForumPost>();
var expr = PredicateBuilder.New<QuestionsForumPost>(true);
if (forumQuestionCriteria.StartDate.HasValue && forumQuestionCriteria.EndDate.HasValue)
{
var dateStart = forumQuestionCriteria.StartDate?.Date;
var dateEnd = forumQuestionCriteria.EndDate?.Date.AddDays(+1);
dateQuery = dateQuery.Start(qf => (qf.DateCreated >= dateStart && qf.DateCreated <= dateEnd));
expr.And(dateQuery);
}
if (!String.IsNullOrEmpty(forumQuestionCriteria.Title))
{
var wordsArrFromTitle = forumQuestionCriteria.Title.ToLower().Split(' ');
var predicate = ContainsInTitle(wordsArrFromTitle);
expr = expr.Or(predicate);
criteriaKeyWords += string.Join(",", wordsArrFromTitle);
}
var result = await query
.Include(qf => qf.SocialInteractions)
.AsExpandable()
.Where(expr)
.Skip((paginationFilter.PageNumber - 1) * paginationFilter.PageSize)
.Take(paginationFilter.PageSize)
.ToListAsync();
}
public static Expression<Func<QuestionsForumPost, bool>> ContainsInTitle(
params string[] keywords)
{
var predicate = PredicateBuilder.False<QuestionsForumPost>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or(qf => qf.Title.Contains(temp));
}
return predicate;
}
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 my custom Visitor which looks to right and left and changes parameters to constant.
I know that just change node is not possible.
I should return new lambda expression which contains constants instead parameters.
But I can not create an expression myself :(
I have this code:
public class ParametersTransformToConstantVisitor : ExpressionVisitor
{
private Dictionary<string, ConstantExpression> parameters = new Dictionary<string, ConstantExpression>();
public ParametersTransformToConstantVisitor(Dictionary<string, ConstantExpression> parameters)
{
this.parameters = parameters;
}
protected override Expression VisitBinary(BinaryExpression node)
{
var constExprLeftName = new Lazy<string>(() => ((ParameterExpression) node.Left)?.Name);
var constExprRightName = new Lazy<string>(() => ((ParameterExpression) node.Right)?.Name);
var constExprName = new Lazy<string>(() => ((ParameterExpression) node.Reduce())?.Name);
ParameterExpression leftParam = null;
ParameterExpression rightParam = null;
if (node.NodeType == ExpressionType.Parameter && parameters.ContainsKey(constExprName.Value))
{
return parameters[constExprName.Value];
}
if (node.Left.NodeType == ExpressionType.Parameter && parameters.ContainsKey(constExprLeftName.Value))
{
leftParam = (ParameterExpression) node.Left;
}
if (node.Right.NodeType == ExpressionType.Parameter && parameters.ContainsKey(constExprLeftName.Value))
{
rightParam = (ParameterExpression) node.Right;
}
if (leftParam != null || rightParam != null)
{
//return Expression.Lambda();
}
return base.VisitBinary(node);
}
}
Help me to build lambda expression, please
It feels like all you actually need here is:
protected override Expression VisitParameter(ParameterExpression node)
=> parameters.TryGetValue(node.Name, out var ce) ? (Expression)ce : node;
protected override Expression VisitLambda<T>(Expression<T> node)
=> Expression.Lambda(Visit(node.Body), node.Parameters); // don't visit the parameters
i.e. whenever the visitor sees a ParameterExpression, if there is a corresponding item in the parameters map, use that value.
The override on VisitLambda is because VisitLambda still needs to return a lambda of the same shape, and the default implementation would also visit (and thus swap) out the parameters from the declaration.
It is the visitor's job to worry about reassembling the tree around your changes.
Note, however, that if you are trying to create a parameterless lambda, you might also need to rewrite the root. Or you could just use the .Body and forget about the parameters.
Example:
Expression<Func<int, int, string>> add = (x, y) => ((2 * x) + y).ToString();
Console.WriteLine(add);
var args = new Dictionary<string, ConstantExpression>
{
["x"] = Expression.Constant(4),
["y"] = Expression.Constant(1),
};
var visitor = new ParametersTransformToConstantVisitor(args);
var result = (LambdaExpression)visitor.Visit(add);
Console.WriteLine(result);
which gives:
(x, y) => ((2 * x) + y).ToString()
(x, y) => ((2 * 4) + 1).ToString()
You can make this into a parameterless lambda via:
var withoutArgs = Expression.Lambda<Func<string>>(result.Body);
Console.WriteLine(withoutArgs);
which gives:
() => ((2 * 4) + 1).ToString()
minor addition: you might also want to simplify in the visitor:
protected override Expression VisitBinary(BinaryExpression node)
{
var visited = base.VisitBinary(node);
if(visited is BinaryExpression be
&& be.Method == null && be.Conversion == null
&& !be.IsLifted
&& be.Left is ConstantExpression left
&& be.Right is ConstantExpression right)
{
object val;
switch(be.NodeType)
{
case ExpressionType.Add:
val = (dynamic)left.Value + (dynamic)right.Value;
break;
case ExpressionType.Multiply:
val = (dynamic)left.Value * (dynamic)right.Value;
break;
case ExpressionType.Subtract:
val = (dynamic)left.Value - (dynamic)right.Value;
break;
case ExpressionType.Divide:
val = (dynamic)left.Value / (dynamic)right.Value;
break;
default:
return visited; // unknown
}
return Expression.Constant(
Convert.ChangeType(val, visited.Type), visited.Type);
}
return visited;
}
This changes the outputs to:
(x, y) => ((2 * x) + y).ToString()
(x, y) => 9.ToString()
() => 9.ToString()
and we could possibly also even hoist the ToString()!
protected override Expression VisitMethodCall(MethodCallExpression node)
{
var visited = base.VisitMethodCall(node);
if (visited is MethodCallExpression mce)
{
if ((mce.Object == null || mce.Object is ConstantExpression)
&& mce.Arguments.All(x => x is ConstantExpression))
{
var obj = (mce.Object as ConstantExpression)?.Value;
var args = mce.Arguments.Select(x => ((ConstantExpression)x).Value).ToArray();
var result = mce.Method.Invoke(obj, args);
return Expression.Constant(result, mce.Type);
}
}
return visited;
}
which now gives us:
(x, y) => ((2 * x) + y).ToString()
(x, y) => "9"
() => "9"
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()))
);
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();