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;
}
Related
Good Day To you all
I have issue with using 'predicate'
I'm trying to make function to return the results of some search text
here is the body of the function
public List<SaddadVM> GetAllBills(int Page = 1, int Take = 10, int? SearchField = null, string SearchItem = null)
{
var predicate = PredicateBuilder.New<SaddadVM>();
if (!string.IsNullOrWhiteSpace(SearchItem))
{
predicate = predicate.And(x => x.BillAcct == SearchItem);
}
predicate = predicate.And(x => x.Id != 0);
var bill = (from d in _context.Saddad
join R in _context.Requests on d.ReqId equals R.Id
join l in _context.Documenter on R.DocumenterId equals l.Id
where d.RecordStatus != GeneralEnums.RecordStatus.Deleted
select new SaddadVM
{
BillAcct = d.BillAcct,
ReqID = d.ReqId,
DocName = l.FullName,
DueDt = d.DueDt,
BillAmount = d.BillAmount
}).Where(predicate).Skip((Page - 1) * Take).Take(Take).ToList();
return bill;
}
Knowing that the the Bill object should return at least 2 depending on running the same query on DB as follows :
enter image description here
I have next table:
MyTable
(
ParentId Integer,
Type Integer,
ProdId String,
Date DateTime,
Status Integer
);
I want to query as next:
var res = from tout in myTable.Where(t1 => t1.Type == 1)
join tin in myTable.Where(t2 => t2.Type != 1)
on tout.ParentId equals tin.ParentId
where tout.ProdId == tin.ProdId && tout.Status > tin.Status
orderby tout.Date
select new MyTableStructure
{
...
};
How to write same as IQueryable using lambda?
Something like this
var query1 = myTable.Where(t1 => t1.Type == 1);
var query2 = myTable.Where(t2 => t2.Type != 1);
var join = query1.Join(query2, x => x.ParentId, y => y.ParentId, (query1, query2) => new { query1 , query2 }).Where(o => o.query1.ProdId == o.qyuery2.prodId).......
your order by next and Something
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'm trying to create some methode for searching and filtring data in databese using c# and asp.net mvc 4 (linq)
public ActionResult Search_Names_Using_Location(string b,string d, int c=0,int Id=0)
{
ViewBag.Locations = db.Locations.ToList();
var agentlocation = new AgentLocationViewModel();
agentlocation.agents = new List<Agent>();
agentlocation.agents = (from a in db.Agents
where a.LocationId == Id
&& (a.LocationName == b)
&& (a.age > c )
select a).ToList();
return View(agentlocation);
}
The problem is that user can let some texboxes empty, so the value of Id or a or b can be null so the query will get nothing.
Is their any suggestions to do that (i can go with if else but that's hard if i have 7 or 8 strings)?
You can check for null inside query
public ActionResult Search_Names_Using_Location(string b,string d,
int c=0,int Id=0,)
{
ViewBag.Locations = db.Locations.ToList();
var agentlocation = new AgentLocationViewModel();
agentlocation.agents = new List<Agent>();
var noId = string.IsNullOrWhitespace(Id);
var noB = string.IsNullOrWhitespace(b);
agentlocation.agents = (from a in db.Agents
where (noId || a.LocationId == Id)
&& (noB || a.LocationName == b)
&& (a.age > c )
select a).ToList();
return View(agentlocation);
}
If you have AND conditions only you can use
var query = db.Agents;
if (Id != 0)
{
query = query.Where(x => x.LocationId == Id)
}
if (!string.IsNullOrWhitespace(b))
{
query = query.Where(x => x.LocationName == b)
}
...
var result = query.ToList(); // actual DB call
This will remove useless empty conditions, like WHERE (0 = 0 OR LocationId = 0)
In case of OR conditions and combinations you can take a look at PredicateBuilder
So you can use Or and And predicate combinations like this:
IQueryable<Product> SearchProducts (params string[] keywords)
{
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);
}