foreach or switch messing up linq query? - linq

Trying to build up a query to filter on data in the following manner works OK, returning users filtered by whatever filters are in the FilterNamesAndValues parameter.
GetAllUsersFiltered(..., Dictionary<string,string> FilterNamesAndValues)
{
....
List<DataContracts.IUser> lstUsers = new List<DataContracts.IUser>();
....
var query = from u in lstUsers select u;
string firstName = string.Empty;
FilterNamesAndValues.TryGetValue("FirstName", out firstName);
query = query.Where(u => u.FirstName == firstName);
string company = string.Empty;
FilterNamesAndValues.TryGetValue("Company", out company);
query = query.Where(u => u.CompanyName == company);
....
return query.ToList();
}
The example below however doesn't work and I can't see why:
GetAllUsersFiltered(..., Dictionary<string,string> FilterNamesAndValues)
{
....
List<DataContracts.IUser> lstUsers = new List<DataContracts.IUser>();
....
var query = from u in lstUsers select u;
foreach (KeyValuePair<string, string> kv in FilterNamesAndValues)
{
if (kv.Value != null)
{
switch (kv.Key)
{
case "FirstName":
query = query.Where(u => u.FirstName == kv.Value);
break;
case "Company":
query = query.Where(u => u.CompanyName == kv.Value);
break;
}
}
}
return query.ToList();
}
After the application has hit the first switch case, I can do a query.ToList() and see a row in there. But by the time the execution has gone around the loop to hit the second filter, query.ToList() returns nothing. The query is not filtered successively the way it was in the first example and worse than that, the filter conditions have effectively been lost. There's probably an obvious explanation for this, but right now I can't see it.

The problem is that you're closing over kv in the foreach, but the query is executed using deferred execution. This causes it to close over the wrong value. For details on what's happening, I'd recommend Eric Lippert's post titled "Closing over the loop variable considered harmful".
You can solve this via a temporary:
foreach (KeyValuePair<string, string> kvOriginal in FilterNamesAndValues)
{
// Make a temporary in the correct scope!
KeyValuePair<string, string> kv = kvOriginal;
if (kv.Value != null)
{
switch (kv.Key)
{
case "FirstName":
query = query.Where(u => u.FirstName == kv.Value);
break;
case "Company":
query = query.Where(u => u.CompanyName == kv.Value);
break;
}
}
}

Related

Why can't I compare two fields in a search predicate in Sitecore 7.5?

I am trying to build a search predicate in code that compares two fields in Sitecore and I am getting a strange error message. Basically I have two date fields on each content item - FirstPublishDate (the date that the content item was first published) and LastPublishDate (the last date that the content item was published). I would like to find all content items where the LastPublishDate falls within a certain date range AND where the LastPublishDate does not equal the FirstPublishDate. Using Linq here is my method for generating the predicate...
protected Expression<Func<T, Boolean>> getDateFacetPredicate<T>() where T : MySearchResultItem
{
var predicate = PredicateBuilder.True<T>();
foreach (var facet in myFacetCategories)
{
var dateTo = System.DateTime.Now;
var dateFrom = dateTo.AddDays(facet.Value*-1);
predicate = predicate.And(i => i.LastPublishDate.Between(dateFrom, dateTo, Inclusion.Both)).And(j => j.LastPublishDate != j.FirstPublishDate);
}
return predicate;
}
Then I use this predicate in my general site search code to perform the search as follows: the above predicate gets passed in to this method as the "additionalWhere" parameter.
public static SearchResults<T> GeneralSearch<T>(string searchText, ISearchIndex index, int currentPage = 0, int pageSize = 20, string language = "", IEnumerable<string> additionalFields = null,
Expression<Func<T, Boolean>> additionalWhere = null, Expression<Func<T, Boolean>> additionalFilter = null, IEnumerable<string> facets = null,
Expression<Func<T, Boolean>> facetFilter = null, string sortField = null, SortDirection sortDirection = SortDirection.Ascending) where T : SearchResultItem {
using (var context = index.CreateSearchContext()) {
var query = context.GetQueryable<T>();
if (!string.IsNullOrWhiteSpace(searchText)) {
var keywordPred = PredicateBuilder.True<T>();
// take into account escaping of special characters and working around Sitecore limitation with Contains and Equals methods
var isSpecialMatch = Regex.IsMatch(searchText, "[" + specialSOLRChars + "]");
if (isSpecialMatch) {
var wildcardText = string.Format("\"*{0}*\"", Regex.Replace(searchText, "([" + specialSOLRChars + "])", #"\$1"));
wildcardText = wildcardText.Replace(" ", "*");
keywordPred = keywordPred.Or(i => i.Content.MatchWildcard(wildcardText)).Or(i => i.Name.MatchWildcard(wildcardText));
}
else {
keywordPred = keywordPred.Or(i => i.Content.Contains(searchText)).Or(i => i.Name.Contains(searchText));
}
if (additionalFields != null && additionalFields.Any()) {
keywordPred = additionalFields.Aggregate(keywordPred, (current, field) => current.Or(i => i[field].Equals(searchText)));
}
//query = query.Where(i => (i.Content.Contains(searchText) || i.Name.Contains(searchText))); // more explicit call to check the content or item name for our term
query = query.Where(keywordPred);
}
if (language == string.Empty) {
language = Sitecore.Context.Language.ToString();
}
if (language != null) {
query = query.Filter(i => i.Language.Equals(language));
}
query = query.Page(currentPage, pageSize);
if (additionalWhere != null) {
query = query.Where(additionalWhere);
}
if (additionalFilter != null) {
query = query.Filter(additionalFilter);
}
query = query.ApplySecurityFilter();
FacetResults resultFacets = null;
if (facets != null && facets.Any()) {
resultFacets = facets.Aggregate(query, (current, fname) => current.FacetOn(i => i[fname])).GetFacets();
}
// calling this before applying facetFilter should allow us to get a total facet set
// instead of just those related to the current result set
// var resultFacets = query.GetFacets();
// apply after getting facets for more complete facet list
if (facetFilter != null) {
query = query.Where(facetFilter);
}
if (sortField != null)
{
if (sortDirection == SortDirection.Ascending)
{
query = query.OrderBy(x => x[sortField]);
}
else
{
query = query.OrderByDescending(x => x[sortField]);
}
}
var results = query.GetResults(); // this enumerates the actual results
return new SearchResults<T>(results.Hits, results.TotalSearchResults, resultFacets);
}
}
When I try this I get the following error message:
Server Error in '/' Application.
No constant node in query node of type: 'Sitecore.ContentSearch.Linq.Nodes.EqualNode'. Left: 'Sitecore.ContentSearch.Linq.Nodes.FieldNode'. Right: 'Sitecore.ContentSearch.Linq.Nodes.FieldNode'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NotSupportedException: No constant node in query node of type: 'Sitecore.ContentSearch.Linq.Nodes.EqualNode'. Left: 'Sitecore.ContentSearch.Linq.Nodes.FieldNode'. Right: 'Sitecore.ContentSearch.Linq.Nodes.FieldNode'.
Source Error:
Line 548: FacetResults resultFacets = null;
Line 549: if (facets != null && facets.Any()) {
Line 550: resultFacets = facets.Aggregate(query, (current, fname) => current.FacetOn(i => i[fname])).GetFacets();
Line 551: }
Line 552: // calling this before applying facetFilter should allow us to get a total facet set
From what I can understand about the error message it seems to not like that I am trying to compare two different fields to each other instead of comparing a field to a constant. The other odd thing is that the error seems to be pointing to a line of code that has to do with aggregating facets. I did a Google search and came up with absolutely nothing relating to this error. Any ideas?
Thanks,
Corey
I think what you are trying is not possible, and if you look at this that might indeed be the case. A solution that is given there is to put your logic in the index: create a ComputedField that checks your dates and puts a value in the index that you can search on (can be a simple boolean).
You will need to split your logic though - the query on the date range can still be done in the predicate (as it is relative to the current date) but the comparison of first and last should be done on index time instead of on query time.

C# linq and sorting

With linq I want to use order by with specific column but I need two switches because i don't know how to use desc or asc in one
public class CustomersRepository : RepositoryBase<Customers>
{
public List<Customers> GetAll(CustomersProperties property, SortEnum sortEnum, int page, int limit)
{
var query = _context.Set<Customers>();
switch (sortEnum)
{
case SortEnum.Ascending:
switch (property)
{
case CustomersProperties.Name:
query = query.OrderBy(x => x.Name);
break;
case CustomersProperties.Surname:
query = query.OrderBy(x => x.Lastname);
break;
default:
throw new ArgumentOutOfRangeException("property");
}
break;
case SortEnum.Descending:
break;
default:
throw new ArgumentOutOfRangeException("sortEnum");
}
return query.Skip(page * limit)
.Take(limit).ToList();
}
}
Is it possible to do without two switch cases?
Be aware that query.OrderBy(x => x.Name); does not do anything since the sorted collection is returned from OrderBy, and you're not capturing that return.
That said, there's not a way to "dynamically" choose the direction in Linq. However, a conditional switch would be a little cleaner. Another option would be to capture the sort expression in a variable:
Expreccion<Func<Customers, string>> propExp;
switch (property)
{
case CustomersProperties.Name:
propExp = ((Customers)x => x.Name)
break;
case CustomersProperties.Surname:
propExp = ((Customers)x => x.Lastname);
break;
default:
throw new ArgumentOutOfRangeException("property");
}
query = sortEnum == SortEnum.Ascending
? query.OrderBy(propExp);
: query.OrderByDescending(propExp);
You could make your own overload, something like this:
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(
this IQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector, SortEnum sort) {
switch (sort) {
case SortEnum.Ascending:
return source.OrderBy(keySelector);
case SortEnum.Descending:
return source.OrderByDescending(keySelector);
default:
throw new ArgumentOutOfRangeException("sort");
}
}
// later on..
query = query.OrderBy(x => x.LastName, sortEnum);
You can prepare sortProperty Expression first and then use it with either OrderBy or OrderByDescending:
public List<Customers> GetAll(CustomersProperties property, SortEnum sortEnum, int page, int limit)
{
var query = _context.Set<Customers>();
Expression<Func<Customers, string>> sortProperty;
switch (property)
{
case CustomersProperties.Name:
sortProperty = x => x.Name;
break;
case CustomersProperties.Surname:
sortProperty = x => x.Lastname;
break;
default:
throw new ArgumentOutOfRangeException("property");
}
switch (sortEnum)
{
case SortEnum.Ascending:
query = query.OrderBy(sortProperty);
break;
case SortEnum.Descending:
query = query.OrderByDescending(sortProperty);
break;
default:
throw new ArgumentOutOfRangeException("sortEnum");
}
return query.Skip(page * limit)
.Take(limit).ToList();
}

The method 'OrderBy' must be called before the method 'Skip' Exception

I was trying to implement the jQgrid using MvcjQgrid and i got this exception.
System.NotSupportedException was unhandled by user code
Message=The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.
Though OrdeyBy is used before Skip method why it is generating the exception? How can it be solved?
I encountered the exception in the controller:
public ActionResult GridDataBasic(GridSettings gridSettings)
{
var jobdescription = sm.GetJobDescription(gridSettings);
var totalJobDescription = sm.CountJobDescription(gridSettings);
var jsonData = new
{
total = totalJobDescription / gridSettings.PageSize + 1,
page = gridSettings.PageIndex,
records = totalJobDescription,
rows = (
from j in jobdescription
select new
{
id = j.JobDescriptionID,
cell = new[]
{
j.JobDescriptionID.ToString(),
j.JobTitle,
j.JobType.JobTypeName,
j.JobPriority.JobPriorityName,
j.JobType.Rate.ToString(),
j.CreationDate.ToShortDateString(),
j.JobDeadline.ToShortDateString(),
}
}).ToArray()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
}
GetJobDescription Method and CountJobDescription Method
public int CountJobDescription(GridSettings gridSettings)
{
var jobdescription = _dataContext.JobDescriptions.AsQueryable();
if (gridSettings.IsSearch)
{
jobdescription = gridSettings.Where.rules.Aggregate(jobdescription, FilterJobDescription);
}
return jobdescription.Count();
}
public IQueryable<JobDescription> GetJobDescription(GridSettings gridSettings)
{
var jobdescription = orderJobDescription(_dataContext.JobDescriptions.AsQueryable(), gridSettings.SortColumn, gridSettings.SortOrder);
if (gridSettings.IsSearch)
{
jobdescription = gridSettings.Where.rules.Aggregate(jobdescription, FilterJobDescription);
}
return jobdescription.Skip((gridSettings.PageIndex - 1) * gridSettings.PageSize).Take(gridSettings.PageSize);
}
And Finally FilterJobDescription and OrderJobDescription
private static IQueryable<JobDescription> FilterJobDescription(IQueryable<JobDescription> jobdescriptions, Rule rule)
{
if (rule.field == "JobDescriptionID")
{
int result;
if (!int.TryParse(rule.data, out result))
return jobdescriptions;
return jobdescriptions.Where(j => j.JobDescriptionID == Convert.ToInt32(rule.data));
}
// Similar Statements
return jobdescriptions;
}
private IQueryable<JobDescription> orderJobDescription(IQueryable<JobDescription> jobdescriptions, string sortColumn, string sortOrder)
{
if (sortColumn == "JobDescriptionID")
return (sortOrder == "desc") ? jobdescriptions.OrderByDescending(j => j.JobDescriptionID) : jobdescriptions.OrderBy(j => j.JobDescriptionID);
return jobdescriptions;
}
The exception means that you always need a sorted input if you apply Skip, also in the case that the user doesn't click on a column to sort by. I could imagine that no sort column is specified when you open the grid view for the first time before the user can even click on a column header. To catch this case I would suggest to define some default sorting that you want when no other sorting criterion is given, for example:
switch (sortColumn)
{
case "JobDescriptionID":
return (sortOrder == "desc")
? jobdescriptions.OrderByDescending(j => j.JobDescriptionID)
: jobdescriptions.OrderBy(j => j.JobDescriptionID);
case "JobDescriptionTitle":
return (sortOrder == "desc")
? jobdescriptions.OrderByDescending(j => j.JobDescriptionTitle)
: jobdescriptions.OrderBy(j => j.JobDescriptionTitle);
// etc.
default:
return jobdescriptions.OrderBy(j => j.JobDescriptionID);
}
Edit
About your follow-up problems according to your comment: You cannot use ToString() in a LINQ to Entities query. And the next problem would be that you cannot create a string array in a query. I would suggest to load the data from the DB with their native types and then convert afterwards to strings (and to the string array) in memory:
rows = (from j in jobdescription
select new
{
JobDescriptionID = j.JobDescriptionID,
JobTitle = j.JobTitle,
JobTypeName = j.JobType.JobTypeName,
JobPriorityName = j.JobPriority.JobPriorityName,
Rate = j.JobType.Rate,
CreationDate = j.CreationDate,
JobDeadline = j.JobDeadline
})
.AsEnumerable() // DB query runs here, the rest is in memory
.Select(a => new
{
id = a.JobDescriptionID,
cell = new[]
{
a.JobDescriptionID.ToString(),
a.JobTitle,
a.JobTypeName,
a.JobPriorityName,
a.Rate.ToString(),
a.CreationDate.ToShortDateString(),
a.JobDeadline.ToShortDateString()
}
})
.ToArray()
I had the same type of problem after sorting using some code from Adam Anderson that accepted a generic sort string in OrderBy.
After getting this excpetion, i did lots of research and found that very clever fix:
var query = SelectOrders(companyNo, sortExpression);
return Queryable.Skip(query, iStartRow).Take(iPageSize).ToList();
Hope that helps !
SP

How to optimize code for Sorting?

I would like to optimize following lines of code for Sorting.
public ViewResult Index(string sortorder, int? pagesize, int? page)
{
int pageSize = pagesize ?? 10;
if (Request.HttpMethod != "GET")
{
page = 1;
pageSize = 10;
}
ViewBag.SelectedPageSize = pageSize;
ViewBag.CurrentSort = sortorder;
ViewBag.FirstNameSortParm = String.IsNullOrEmpty(sortorder) ? "FirstName desc" : "";
ViewBag.LastNameSortParm = sortorder == "LastName" ? "LastName desc" : "LastName";
ViewBag.DepNameSortParm = sortorder == "depName" ? "depName desc" : "depName";
var joined = from tm in db.TabMasters select tm;
switch (sortorder)
{
case "FirstName":
joined = joined.OrderBy(m => m.FirstName);
break;
case "FirstName desc":
joined = joined.OrderByDescending(m => m.FirstName);
break;
case "LastName":
joined = joined.OrderBy(m => m.LastName);
break;
case "LastName desc":
joined = joined.OrderByDescending(m => m.LastName);
break;
case "depName":
joined = joined.OrderBy(m => m.depName);
break;
case "depName desc":
joined = joined.OrderByDescending(m => m.depName);
break;
default:
joined = joined.OrderBy(m => m.FirstName);
break;
}
int pageIndex = (page ?? 1) - 1;
int start = (pageIndex * pageSize);
ViewBag.TotalRecord = joined.Count();
ViewBag.StartRecord = start + 1;
ViewBag.EndRecord = ((start + pageSize) >= ViewBag.TotalRecord) ? ViewBag.TotalRecord : (start + pageSize);
return View(joined.ToPagedList(pageIndex, pageSize));
}
Because this is very tedious way if i have more the 10 fields to perform sort.
Thanks,
Imdadhusen
It's a bit vague to me what your actual goal is but for the switch part you could use an extension method as the below.
public static class SortExtensions
{
public static IEnumerable<T> SortByField<T>(this IEnumerable<T> sequence, string sortOrder)
{
var tokens = sortOrder.Trim().Split(' ');
var field = tokens[0];
var direction = tokens.Skip(1).Single().ToLower();
var prop = typeof(T).GetProperty(field);
return direction == "desc"
? sequence.OrderByDescending(m => prop.GetValue(m, null))
: sequence.OrderBy(m => prop.GetValue(m, null));
}
}
It will make a very simplified parsing of the sort order. It puts the responsibility on the calling party which is generally not what you want to do, so you might want some error handling in case the sortorder string does not fulfill the requirements.
from the sortorder string it fetches a name used to identify a property which can be used to fetch the value used for sorting.
you can use it like this:
db.TabMasters.SortByField(sortOrder)
EDIT based on comment:
The line typeof(T).GetProperty(field) is fragile in the absence of any error handling. It relies on the first token to be a name of a public property of the type T. It will return null if the name doesn't match a property. Including if it matches a Field name. A similar function exist for getting a FieldInfo
prop.GetField(field) will return a fieldinfo object of there's a public field with the given name otherwise null. To get the value of a field simply omit the last parameter to the GetValue call.
You should take a look at Linq.DynamicQuery.
There's more info in this blogpost http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
The library lets you write following code:
var query = northwind.Products
.Where("CategoryID = 3 AND UnitPrice > 3")
.OrderBy("SupplierID");
instead of
var query = from p in northwind.Products
where p.CategoryID == 3 && p.UnitPrice > 3
orderby p.SupplierID
select p;
If you want to add the sortdirection:
var query = northwind.Products.OrderBy("SupplierID Descending");

Linq / lambda expressions

Trying something with Linq / Lambda's, but have no idea where to search.
I'm working on some simple sorting in an ASP.net GridView. Here's some sample code:
IQueryable<User> query = (from c in users select c).AsQueryable<User>();
if (isAscending)
{
switch (e.SortExpression)
{
case "Name":
query.OrderBy(c => c.Name);
break;
default:
break;
}
}
else
{
switch (e.SortExpression)
{
case "Name":
query.OrderByDescending(c => c.Name);
break;
default:
break;
}
}
grid.DataSource = query.ToList();
grid.DataBind();
I am, however, unsatisfied with the code as it is very sensitive to errors and requires lots of duplicated code. I'm hoping to solve this using Lambda expressions instead, but I have no idea how. Here's what I would like to go to:
IQueryable<User> query = (from c in users select c).AsQueryable<User>();
var param = null;
switch (e.SortExpression)
{
case "Name":
param = (c => c.Name);
break;
default:
break;
}
if (isAscending)
{
query.OrderBy(param);
}
else
{
query.OrderByDescending(param);
}
grid.DataSource = query.ToList();
grid.DataBind();
Could anyone please help me?
Thanks!
Your code is broken at the moment - you're calling OrderBy/OrderByDescending but not using the result. You need
query = query.OrderBy(param);
etc.
As for conditionally ordering - the problem is that you can't declare the type of param because the type of the ordering key may well vary depending on the sort expression.
As a workaround, you can write your own extension method:
public static IOrderedQueryable<TSource> OrderByWithDirection<TSource, TKey>
(this IQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
bool ascending)
{
return ascending ? source.OrderBy(keySelector)
: source.OrderByDescending(keySelector);
}
Your code would then become:
IQueryable<User> query = (from c in users select c).AsQueryable<User>();
switch (e.SortExpression)
{
case "Name":
query = query.OrderByWithDirection(c => c.Name, isAscending);
break;
// etc
default:
break;
}
(Why are you calling AsQueryable, by the way?)

Resources