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;
}
Related
I have following code,
public List<MemberDto> GetMembers(out int rowCount,int pageIndex,int pageSize, string seachColumn = "", string searchTerm = "", string sortBy = "", string sortDiection = "")
{
var members = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
|| (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m).AsEnumerable();
if (!string.IsNullOrEmpty(sortBy))
{
PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(EFModel.ClientData.Member)).Find(sortBy, true);
members = (sortDiection.ToLower() == "descnding") ? members.OrderByDescending(x => prop.GetValue(x)).ToList() : members.OrderBy(x => prop.GetValue(x)).ToList();
}
rowCount = (!string.IsNullOrEmpty(searchTerm)) ? members.Count() : rowCount = context.Members.Count() ;
members = members.Skip(pageIndex).Take(pageSize).ToList();
List<MemberDto> memberDtos = new List<MemberDto>();
mapper.Map(members, memberDtos);
return memberDtos;
}
In the above code, I seachColumn value can be ("a","b",or ""). When seachColumn = "a" I need to search table data by column MemberNumber based on searchTerm value
When seachColumn = "b" I need to search table data by column LastName. based on searchTerm value
to achieve that I wrote following code.
if(seachBy == "a")
{
var sa = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
select m).AsEnumerable();
}
else if (seachBy == "b")
{
var sa = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m).AsEnumerable();
}
I know, I tried code is bit fool. Have any proper way to do this?
Yes there is a better way to do this. First of all you want to do this all as an IQueryable - Why? - Because as soon as you do .AsEnumerable() or .ToList() the query is executed on the DB Server and the data is loaded into Memory.
So in your code here - because you have called .AsEnumerable() it has loaded all context.Members.Where condition is true into Memory:
var members = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
|| (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m)
.AsEnumerable();
Your second part of code is pagination. What we normally do is write a IQueryable extension methods.
So add the following class with the 2 extension methods to a common location.
public static class IQueryableExtensions
{
public static IQueryable<T> ApplyPagination<T>(this IQueryable<T> source, string sortDirection, string sortBy, int pageNumber, int pageSize)
{
var sortDirectionInternal = sortDirection == "asc" ? "OrderBy" : "OrderByDescending";
var orderBy = sortBy;
if (pageSize != -1) // -1 is for All - I don't apply pagination if pageSize == -1.
{
return source.OrderBy(orderBy, sortDirectionInternal)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
}
return source.OrderBy(orderBy, sortDirection);
}
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, string sortDirection)
{
var type = typeof(T);
// Get Property to Sort By
var property = type.GetProperty(ordering);
// If Property is NULL (not found) - Just use the first Property (Default) to ORDER BY - as this will prevent Exception
if (property == null)
{
property = type.GetProperties().First();
}
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
var resultExp = Expression.Call(typeof(Queryable), sortDirection, new[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
return source.Provider.CreateQuery<T>(resultExp);
}
}
So now your above code will look something like this - I didn't use VS so there might be some sytnax errors:
// NOTE: you will need to include the namespace for the new IQueryableExtensions class
var members = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
|| (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m);
// retrieve count only if you need to of total members that match the above criteria
rowCount = members.Count();
// This is all you need to do! - ApplyPagination(params) -
members = members.ApplyPagination(sortDiection, sortBy, pageIndex, pageSize);
return mapper.Map<List<MemberDto>>(members);
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 the below LINQ query that performs a self-left-outer-join. The querys looks a little complex but is simply doing a self join on itself(purpose if to join each record with the record for it previous business day) and then doing some parameterized filtering.
var newBreakThreshold = decimal.Parse(WebConfigurationManager.AppSettings["NewBreakThreshold"]);
using (var dbContext = new NavFoToBoCompareDbContext())
{
var query = from current in dbContext.NAVSummaries
let currentWD = SqlFunctions.DatePart("dw", current.ValueDate)
let currentPD = DbFunctions.AddDays(current.ValueDate, currentWD == 2 ? -3 : currentWD == 1 ? -2 : -1).Value
join previous in dbContext.NAVSummaries
on new { current.Portfolio, PD = currentPD }
equals new { previous.Portfolio, PD = previous.ValueDate }
into previousGroup
from previous in previousGroup.DefaultIfEmpty() // LEFT OUTER JOIN
select new { outer = current, inner = previous };
if (dateStart.HasValue)
query = query.Where(e => e.outer.ValueDate >= dateStart.Value);
if (dateEnd.HasValue)
query = query.Where(e => e.outer.ValueDate <= dateEnd.Value);
if (!portfolio.Equals("ALL", StringComparison.OrdinalIgnoreCase))
query = query.Where(e => e.outer.Portfolio.Equals(portfolio, StringComparison.OrdinalIgnoreCase));
if (!owner.Equals("ALL", StringComparison.OrdinalIgnoreCase))
query = query.Where(e => e.outer.PortfolioOwner.Equals(owner, StringComparison.OrdinalIgnoreCase));
if (status != 0)
query = query.Where(e => e.outer.Statuses.Any(s => s.StatusId == status));
var query2 = query.Select(s => new
{
BackOfficeNAV = s.outer.BackOfficeNAV,
FrontOfficeNAV = s.outer.FrontOfficeNAV,
Threshold = s.outer.Threshold,
ExtractId = s.outer.ExtractId,
ExtractStatus = s.outer.ExtractStatus,
PortfolioOwner = s.outer.PortfolioOwner,
DateTimeModified = s.outer.DateTimeModified,
MostCorrectNAV = s.outer.MostCorrectNAV,
Comments = s.outer.Comments,
Statuses = s.outer.Statuses,
Extracts = s.outer.Extracts,
Portfolio = s.outer.Portfolio,
ValueDate = s.outer.ValueDate,
DifferencePercent = s.outer.DifferencePercent,
DayOverDayChange = s.outer.DifferencePercent - s.inner.DifferencePercent,
IsChange = s.inner.DifferencePercent != s.outer.DifferencePercent,
PreviousValueDate = s.inner.ValueDate,
PreviousDifferencePercent = s.inner.DifferencePercent
});
query2 = query2.Where(r => "NEW".Equals(breakOption, StringComparison.OrdinalIgnoreCase) ?
((r.DifferencePercent > r.Threshold) && r.IsChange && r.DayOverDayChange > newBreakThreshold) :
"OLD".Equals(breakOption, StringComparison.OrdinalIgnoreCase) ? (r.DifferencePercent > r.Threshold) :
"ALL".Equals(breakOption, StringComparison.OrdinalIgnoreCase));
var resultCount = query2.Count();
}
The query is used in two places. In one method it used for computing the count required for pagination. In another method it is used for getting the actual results from the database. The implementation for getting the actual results for a bigger result set executes successfully, whereas the the Count() query fails with a Timeout exception. Note:Both implementations are exactly the same.
Can someone help me in optimizing this query as well. Thanks in advance.
Not quite sure that's the problem, but at least let try to eliminate the potential effect of the so called Parameter Sniffing Problem by eliminating the dateStart / dateEnd parameters by manually building expression with constant values.
First, a little helper method:
using System;
using System.Linq;
using System.Linq.Expressions;
public static class QueryableUtils
{
public static IQueryable<T> WhereBetween<T>(this IQueryable<T> source, Expression<Func<T, DateTime>> dateSelector, DateTime? startDate, DateTime? endDate)
{
if (startDate == null && endDate == null) return source;
var startCond = startDate != null ? Expression.GreaterThanOrEqual(dateSelector.Body, Expression.Constant(startDate.Value)) : null;
var endCond = endDate != null ? Expression.LessThanOrEqual(dateSelector.Body, Expression.Constant(endDate.Value)) : null;
var predicate = Expression.Lambda<Func<T, bool>>(
startCond == null ? endCond : endCond == null ? startCond : Expression.AndAlso(startCond, endCond),
dateSelector.Parameters[0]);
return source.Where(predicate);
}
}
Then try the following and see if it helps:
//if (dateStart.HasValue)
// query = query.Where(e => e.outer.ValueDate >= dateStart.Value);
//if (dateEnd.HasValue)
// query = query.Where(e => e.outer.ValueDate <= dateEnd.Value);
query = query.WhereBetween(e => e.outer.ValueDate, dateStart, dateEnd);
You can simply use AsParallel() to enable multithreading execution of the linq query.
You should then check your tables' indexes to improve performances.
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]);
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);
}