Dynamic LINQ help for sorting problem - linq

I have a simple IEnumerable collection of Order objects. I want to write a generic sort function that accepts the collection and a sortkey that corresponds to a property on an Order object. I don't want to hardcode the sort instructions for every property. How can I build a dynamic LINQ string that automatically generates the LINQ for me? Here is my SortOrders() function in its current state:
public IEnumerable<Order> SortOrders(IEnumerable<Order> orders, string sortKey)
{
// Sort order records
try
{
IEnumerable<Order> sortedOrders = new List<Order>();
// Extract sort key and direction from combined sortKey input
string _sortKey = this.SortKey.Replace(" ASC", "").Replace(" DESC", "");
bool sortASC = this.SortKey.EndsWith(" ASC");
switch (_sortKey)
{
case "CustomerID":
sortedOrders = (sortASC) ? orders.OrderBy(o => o.CustomerID) : orders.OrderByDescending(o => o.CustomerID);
break;
case "CustomerAddress":
sortedOrders = (sortASC) ? orders.OrderBy(o => o.CustomerAddress) : orders.OrderByDescending(o => o.CustomerAddress);
break;
case "CustomerCity":
sortedOrders = (sortASC) ? orders.OrderBy(o => o.CustomerCity) : orders.OrderByDescending(o => o.CustomerCity);
break;
default:
sortedOrders = orders;
break;
}
return sortedOrders;
}
catch
{
return orders; // return original orders list in the event of errors
}
}
I am looking to replace all of the case sections like this:
case "CustomerID":
sortedOrders = (sortASC) ? orders.OrderBy(o => o.CustomerID) : orders.OrderByDescending(o => o.CustomerID);
with a single directive like this:
sortedOrders = (sortASC) ? orders.OrderBy(o => o.PropertyName) : orders.OrderByDescending(o => o.PropertyName);

You can pass the expression you want to sort in as a Lamba (this is simplified/pseudocode)
SortOrders(IEnumerable<Order> orders, Func<Order, string> func)
{
sortedOrders = orders.OrderBy(func)
....
}
// call:
SortOrders(orders, o => o.CustomerID);

You can use Dynamic LINQ and there's no need for the extension method. Just use the extension in the DynamicQueryable class and specify both the key and direction as a string.
var sorted = orders.OrderBy( "CustomerID desc" );

Related

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();
}

Set the list value using linq

Hi I want to set the value in the list of objects that matches the given condition in the where clause.Is it possible?
Other work around is to get the list of objects using where clause and then iterate using for Or foreach loop and update the value.
listOfRequestAssigned.Where(x => x.RequestedNumber == CurrentRequest);
I have list listOfRequestAssigned of objects and want to update some propery of the objects that match my search criteria.
class Request
{
bool _requestCompleted;
int _requestedNumber;
public int RequestedNumber
{
get { return _requestedNumber; }
set { _requestedNumber = value; }
}
public bool RequestCompleted
{
get { return _requestCompleted; }
set { _requestCompleted = value; }
}
}
I want to update RequestCompleted property of all objects that match criteria using Linq
You can use ForEach in Linq
listOfRequestAssigned.Where(x => x.RequestedNumber == CurrentRequest).ToList().ForEach(x => x.RequestCompleted = true);
if you have more than one update to do,
listOfRequestAssigned.Where(x => x.RequestedNumber == CurrentRequest).ToList().ForEach(x => { x.RequestCompleted = true; x.OtherProperty = value; } );
Where(...) give you a query, not a Request or a List<Request>. Use FirstOrDefault() if you want to have one (or 0) result, or ToList() if you want to have a list of results on wich you can use ForEach().
In general Linq is a query- not an update tool, but you can use a foreach:
var currentRequests = listOfRequestAssigned
.Where(x => x.RequestedNumber == CurrentRequest);
foreach(var req in currentRequests)
{
req.RequestCompleted = true;
}
Since you have specific collection of type List, you can just use ForEach and a conditional set:
listOfRequestAssigned.Foreach(x => { if (x.RequestedNumber == CurrentRequest) x.RequestCompleted = true;}});
If you had a more generic collection IEnumerable, you can use Select in Linq to build a projection where property will be set as desired (original collection will be left untouched!):
listOfRequestAssigned
.Where(x => x.RequestedNumber == CurrentRequest)
.Select(x => { x.RequestCompleted = true; return x; })
You can use to assign boolean value by following on comparing time. This is the very simplest and smart way for bool property.
listOfRequestAssigned.ForEach(x => x.RequestCompleted = x.RequestedNumber
== CurrentRequest);

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 to Entites/IQueryable: Sorting on multiple fields

I have the following that I'd like to sort:
IQueryable<Map> list;
list = from item in ctx.MAP
.Include("C")
.Include("L")
.Include("L.DP")
select item;
return list.OrderBy(m=>(m.L.DP.Name + m.L.Code));
This works, but it sorts alphabetically - so 12 comes before 9. (Assume Code is a numeric field)
What is the best way to sort this so Code is sorted numerically?
You probably want to use the ThenBy extension method to be able to sort by multiple fields ;)
In your case that would be
return list.OrderBy(m=>m.L.DP.Name).ThenBy(m => m.L.Code);
var results = db.Blogs.AsEnumerable()
.Select(sr => new
{
Searchresult = sr,
Words = Regex.Split(sr.Name, #"[^\S\r\n {1,}").Union(Regex.Split(sr.Name2, #"[^\S\r\n]{1,}"))
})
.OrderByDescending(x => x.Words.Count(w => {
foreach (var item in searchTerms)
{
if(w.ToLower().Contains(item))
{
return true;
}
}
return false;
}))
.Select(x => x.Searchresult);

Resources