LINQ to Entites/IQueryable: Sorting on multiple fields - linq

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

Related

enumerable group field using Linq?

I've written a Linq sentence like this:
var fs = list
.GroupBy(i =>
new {
X = i.X,
Ps = i.Properties.Where(p => p.Key.Equals("m")) <<<<<<<<<<<
}
)
.Select(g => g.Key });
Am I able to group by IEnumerable.Where(...) fields?
The grouping won't work here.
When grouping, the runtime will try to compare group keys in order to produce proper groups. However, since in the group key you use a property (Ps) which is a distinct IEnumerable<T> for each item in list (the comparison is made on reference equality not on sequence equality) this will result in a different collection for each element; in other words if you'll have two items:
var a = new { X = 1, Properties = new[] { "m" } };
var b = new { X = 1, Properties = new[] { "m" } };
The GroupBy clause will give you two distinct keys as you can see from the image below.
If your intent is to just project the items into the structure of the GroupBy key then you don't need the grouping; the query below should give the same result:
var fs = list.Select(item => new
{
item.X,
Ps = item.Properties.Where(p => p.Key == "m")
});
However, if you do require the results to be distinct, you'll need to create a separate class for your result and implement a separate IEqualityComparer<T> to be used with Distinct clause:
public class Result
{
public int X { get; set; }
public IEnumerable<string> Ps { get; set; }
}
public class ResultComparer : IEqualityComparer<Result>
{
public bool Equals(Result a, Result b)
{
return a.X == b.X && a.Ps.SequenceEqual(b.Ps);
}
// Implement GetHashCode
}
Having the above you can use Distinct on the first query to get distinct results:
var fs = list.Select(item => new Result
{
X = item.X,
Ps = item.Properties.Where( p => p.Key == "m")
}).Distinct(new ResultComparer());

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

Linq GroupBy to alter list

I have a list of items in an IList<>.Each listitem has a date and a few other fields.
I need to order the list by date and then change the list to only show a date for the first item and effectively set the date field to null for the other items if the date is repeated.
Example:
12/01/2012 500
12/01/2012 700
15/02/2012 900
15/02/2012 1100
27/05/2012 2000
Desired Result:
12/01/2012 500
null 700
15/02/2012 900
null 1100
27/05/2012 2000
Is this possible with the linq group by and order by?
Thanks
LINQ operators are not supposed to change the underlying data. You'd better use regular foreach if you're going to modify the data.
This should probably work:
var groups = items.GroupBy(x => x.Date).ToArray();
foreach (var group in groups)
{
foreach (var item in group.Skip(1)) item.Date = null;
}
I would avoid using such a construction since you'll have to double-check that GroupBy preserves order. Instead I would use something like this:
var sortedItems = items.OrderBy(x => x.Date);
var lastVisitedDate = (DateTime?) null;
foreach (var item in sortedItems)
if (Equals(item.Date, lastVisitedDate)) item.Date = null;
else lastVisitedDate = item.Date;
This should work:
var list = new List<DateItem>();
// Initialization ...
var dups = list.Select((Item,Index) => new{ Item,Index })
.GroupBy(x => x.Item.Date)
.Where(g => g.Count() > 1);
foreach(var dup in dups)
{
foreach (var nullable in dup.OrderBy(x => x.Item.Date).Skip(1))
{
list[nullable.Index].Date = null;
}
}
Assuming your class looks similar to this:
class DateItem {
public DateTime? Date;
public int OtherField;
}
Edit: Here's a working demo: http://ideone.com/cVL4G
One way is to use LINQ to get all of the followers and then set their dates to null in a loop:
// Use ToList() to make sortedItems non-lazy so it won't get ordered each time it's called.
var sortedItems = items.OrderBy(x => x.Date).ToList();
var followers = sortedItems.GroupBy(item => item.Date)
.SelectMany(group => group.Skip(1));
foreach (var follower in followers)
{
follower.Date = null;
}
// Now you can use sortedItems.
Or if you prefer the query syntax:
var followers = from item in sortedItems
group item by item.Date into grp
from follower in grp.Skip(1)
select follower;

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 select decreasing sub-series with Linq

I have a list of prices ordered by date. I need to select all monotonously decreasing values. The following code works:
public static List<DataPoint> SelectDecreasingValues(List<DataPoint> dataPoints)
{
var ret = new List<DataPoint>(dataPoints.Count);
var previousPrice = dataPoints[0].Price;
for (int i = 0; i < dataPoints.Count; i++)
{
if (dataPoints[i].Price <= previousPrice)
{
ret.Add(dataPoints[i]);
previousPrice = dataPoints[i].Price;
}
}
return ret;
}
However, is there a shorter/cleaner way to accomplish it with Linq?
This code is equivalent:
previousPrice = dataPoints[0].Price;
var ret = dataPoints.Where(x => {
if(x.Price <= previousPrice)
{ previousPrice = x.Price; return true;}
return false;
}).ToList();
However, if you don't need to have a list, go with plain enumerables and drop the ToList at the end. That way you can make use of the deferred execution feature built into LINQ.
The following code is also equivalent:
DataPoint previous = dataPoints.FirstOrDefault();
var ret = dataPoints.Where(x => x.Price <= previous.Price)
.Select(x => previous = x).ToList();
This works because of the deferred execution in LINQ. For each item in dataPoints it will first execute the Where part and then the Select part and only then will it move to the second item in dataPoints.
You need to decide which version you want to use. The second one is not as intention revealing as the first one, because you need to know about the internal workings of LINQ.
public IEnumerable<T> WhereMonotonicDecreasing<T>(
IEnumerable<T> source,
Func<T, IComparable> keySelector)
{
IComparable key;
bool first = true;
foreach(T t in source)
{
if (first)
{
key = keySelector(t);
yield return t;
first = false;
}
else
{
IComparable newKey = keySelector(t);
if (newKey.CompareTo(key) < 0)
{
key = newKey;
yield return t;
}
}
}
}
Called by:
dataPoints.WhereMonotonicDecreasing(x => x.Price);
previousPrice = dataPoints[0];
dataPoints.Where(p => p.Price <= previousPrice.Price)
.Select(p => previousPrice = p);
You can then use .ToList() if you really need one.
How about (untested):
return dataPoints.Take(1)
.Concat(dataPoints.Skip(1)
.Zip(dataPoints,
(next, previous) =>
new { Next = next, Previous = previous })
.Where(a => a.Next.Price <= a.Previous.Price)
.Select(a => a.Next))
.ToList();
Essentially, this overlays a "one-deferred" version of the sequence over the sequence to produce "next, previous" tuples and then applies the relevant filters on those tuples. The Take(1) is to pick the first item of the sequence, which it appears you always want.
If you don't care for the readability of the variable names, you could shorten it to:
return dataPoints.Take(1)
.Concat(dataPoints.Skip(1)
.Zip(dataPoints, Tuple.Create)
.Where(a => a.Item1.Price <= a.Item2.Price)
.Select(a => a.Item1))
.ToList();

Resources