Loop to set OrderBy/ThenBy for multiple columns - c#

I'm using DataTables.Mvc library for use with jQuery DataTables.
One of the methods is GetSortedColumns() which returns an array containing configurations for each column to be sorted.
Of interest in this object are the Name and SortDirection properties. Name is also the database table field name. SortDirection is either asc or desc.
At first ThenBy and ThenByDescending were undefined symbols, so I created ordered as IOrderedQueryable. This resolves the symbols, but I don't see any effect of these. Neither OrderBy, OrderByDescending, ThenBy nor ThenByDescending have any effect on the order of records in filteredRecords.
In Controller:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult GetUserSelections([ModelBinder(typeof(DataTablesBinder))] IDataTablesRequest requestModel)
{
// Column Sort
var filteredRecords = db.AspNetSelectorInputs.Select(si => si);
var sortedColumns = requestModel.Columns.GetSortedColumns();
var count = 0;
foreach (var column in sortedColumns)
{
var ordered = filteredRecords as IOrderedQueryable<AspNetSelectorInput>;
filteredRecords =
column.SortDirection == DataTables.Mvc.Column.OrderDirection.Ascendant
? count == 0
? ordered.OrderBy(c => column.Name)
: ordered.ThenBy(c => column.Name)
: count == 0
? ordered.OrderByDescending(c => column.Name)
: ordered.ThenByDescending(c => column.Name);
count++;
}
filteredRecords = filteredRecords.Select(si => si).Skip(requestModel.Start).Take(requestModel.Length);
....
Can anyone see why this doesn't affect ordering of filteredRecords?
Is there a better way?

It is sorting, on exactly what you've asked it to. But the lambda expressions aren't doing what you think. For example, you're doing .OrderBy(c => column.Name), which is sorting using a literal value of the name of the column which has the same value for every item in the collection (notice how the thing it is sorting on is not affected by c), so it appears not to sort your collection. For example, you might as well be doing .OrderBy(c => "Hello").
You would need to do something like .OrderBy(c => c.YourChoiceOfPropertyName). Except you can't do that because (presumably) the name of the property is a string value in column.Name. So you'll need to use reflection within the lambda to get the value of that property using c as the instance. This will need fixing on all the lambdas. For example, inside the loop:
var propertyInfo=typeof(AspNetSelectorInput)
.GetProperty(column.Name);
And replacement lambda expressions:
c=>propertyInfo.GetValue(c)
P.S. the two instances of .Select(si => si) seem to be redundant, unless I am missing something.

Related

linq groupby plus select returning element type rather than value

Novice C#, veteran DBA and brand new to LINQ. I'm trying to understand why a select clause, within context of a groupBy, is returning a type name (in this case "System.Linq.Enumerable+WhereSelectEnumerableIterator`2[CasesReportParser.Requisition,System.String]") rather than a field value.
My ultimate confusion, I believe, is generically how to include additional fields in a groupBy result with those additional fields not being grouped on (not otherwise available in g.Key).
Here is the code and it is the conditional, ExamType, on the last line that is returning the type name. Thank you.
var ListFinal = rows
.GroupBy(l => new { ExamDate = ParseDate(l.ExamDate), l.PatientID, l.PatientFirst, l.PatientLast, l.Birthdate, l.SiteName })
.Select(g => new {
//ExamDate = g.Select(l => l.ExamDate),//.ToString().Substring(0,10), //.ToString().Split(' ')[0]
ExamDate = g.Key.ExamDate,
PatientID = g.Key.PatientID,
PatientFirst = g.Key.PatientFirst,
PatientLast = g.Key.PatientLast,
Birthdate = g.Key.Birthdate,
SiteName = g.Key.SiteName,
ReqCount = g.Select(l => l.RequisitionNumber).Distinct().Count(),
ExamCount = g.Select(l => l.ExamID).Distinct().Count(),
ExamType = (g.Select(l => l.ExamID).Distinct().Count()>1 ? "multiple" : g.Select(l => l.ExamType).ToString())
});
You might want to add .First, because it is returning a collection.
g.Select(l => l.ExamType).First().ToString()
First of Select doesn´t return any elements at all. In fact it does not even return a collection, but an iterator that can iterate over a collection.
So just calling Select on a collection won´t do anything at all, only calling MoveNext on that iterator will perform the query. This can be done in many difefrent ways, one of which is by calling First or FirstOrDefault, which seems what you should use in your case. First will throw an exception if no element was returned, FirstOrDefault however will return the default-value for the type of the iterator (null for reference-types).
Having said this you probably need this in your Select:
ExamType = g.Select(l => l.ExamID).Distinct().Count() > 1 ?
"multiple" :
g.FirstOrDefault(l => l.ExamType)?.ToString()
As FirstOrDefault may return null you´d get a NullReferenceException, which is why you should use the ?.-operator.

How to RemoveAll list rows matching a condition using linq?

I'm trying to remove all records in a List<T> matching a where condition. What I did find in linq is the RemoveAll() method but it seems to only work by removing properties matching a condition instead of the complete row in the list.
So I did try a remove all as suggested here in conjunction with a Where clause which causes an "argument null exception".
Question:
How can you RemoveAll list rows matching a condition using linq?
//Sort the list by the UpdatedTime time descending
//Remove all records in list that are older than today's date and status equal to BB. Then order the remaining records desc.
var cuttOff = DateTime.UtcNow.AddDays(-10);
List<Escalation> escHistorySorted = escHistory.RemoveAll.Where(x => x.UpdatedTime <= cuttOff && x.status == "BB").OrderByDescending(d => d.UpdatedTime).ToList();
It looks like you're trying to do too much at once.
Remove the records first (move the predicate from your where clause directly inside RemoveAll), and then sort them.
var cuttOff = DateTime.UtcNow.AddDays(-10);
escHistory.RemoveAll(x => x.UpdatedTime <= cuttOff && x.status == "BB");
List<Escalation> escHistorySorted = escHistory.OrderByDescending(d => d.UpdatedTime).ToList();
The return value of RemoveAll is an integer representing the number of records removed, so you can't simply order the result of calling that method.

Filter in linq with ID's in a List<int>

I need do a filter that request data with a parameter included in a list.
if (filter.Sc.Count > 0)
socios.Where(s => filter.Sc.Contains(s.ScID));
I try on this way but this not work, I tried also...
socios.Where( s => filter.Sc.All(f => f == s.ScID));
How I can do a filter like this?
socios.Where(s => filter.Sc.Contains(s.ScID));
returns a filtered query. It does not modify the query. You are ignoring the returned value. You need something like:
socios = socios.Where(s => filter.Sc.Contains(s.ScID));
but depending on the type of socios the exact syntax may be different.
In addition to needing to use the return value of your LINQ .Where(), you have a potential logic error in your second statement. The equivalent logic for a .Contains() is checking if Any of the elements pass the match criteria. In your case, the second statement would be
var filteredSocios = socios.Where( s => filter.Sc.Any(f => f == s.ScID));
Of course if you can compare object-to-object directly, the .Contains() is still adequate as long as you remember to use the return value.

Generic List Object OrderBy dynamic Column name

I am trying to sort data which is from Generic List object.
By using below code, I can sort my data according to Title column.
But what I would like to do is I would like to sort data according to parameter value called sidx.
public ActionResult ListingGridData(string sidx, string sord, int page, int rows)
{
int currentPage = Convert.ToInt32(page) - 1;
int totalRecords = 0;
var SeminarList = (List<Seminar>)null;
if(sord.Equals("asc"))
SeminarList = seminarRepository.AllSeminarList().Seminar_Masters
.OrderBy(x => x.Title )
.Skip(currentPage * rows)
.Take(rows)
.ToList();
else
SeminarList = seminarRepository.AllSeminarList().Seminar_Masters
.OrderByDescending(x => x.Title)
.Skip(currentPage * rows)
.Take(rows)
.ToList();
totalRecords = seminarRepository.AllSeminarList().Seminar_Masters.Count;
var totalPages = (int)Math.Ceiling((float)totalRecords / (float)rows);
....
}
So I have modified my code like that, but it does not work.
It does not sort as I have expected.
SeminarList = seminarRepository.AllSeminarList().Seminar_Masters
.OrderBy(x => sidx)
.Skip(currentPage * rows)
.Take(rows)
.ToList();
The object which I now use is pure List object, not IQueryable object.
As for sorting purpose, I don't want to go back to SQL select statement, I would like to sort data at Controller layer.
Please let me get any suggestions.
You must order by a column that is in your result set. If the sidx is not part of your resultset then it is like ordering by a constant. Thus every row will be the same and it will not REALLY be ordered. Let's assume sidx's value is 10, then it would be like ordering this resultset:
Row 1 | 10
Row 2 | 10
Row 3 | 10
...
You can see how the ordering would be pointless
What you need to do is add sidx to your result set if that is what you want:
SeminarList = seminarRepository.AllSeminarList().Seminar_Masters
.OrderBy(x => x.sidx)
.Skip(currentPage * rows)
.Take(rows)
.ToList();
See answer for dynamic where and orderby using some helper
methods here
where can I find a good example of using linq & lambda expressions to generate dynamic where and orderby sql?
So you're saying that the calling method will provide a Property name via the sidx parameter, and you want to sort by that property?
The easiest way is to use the Dynamic LINQ library. Another option would be to use reflection to find the property you're looking for, then build a lambda expression from it.

Location of XElement when querying over IEnumerable using LINQ

I have a linq query that is querying over IEnumberable. When I have a matching element for my where clause I would like to know the position of the element in the IEnumberable.
var result = from e in elements
where (string) e.Attribute("class") == "something"
select e.Position();
The e.Position() of course does not compile. The value of e.Position() would be the position of the selected element in the elements IEnumberable.
Any ideas on how to do this?
You need to use the overloaded Select method that allows for an index since that capability is not available in query syntax.
elements.Select((e, i) => new { Element = e, Index = i })
.Where(item => (string)item.Element.Attribute("class") == "something")
.Select(item => item.Index);
If you're using .NET 4.0 then you can use the (new) Zip method and write the same thing using the query syntax as well. It creates some temporary objects, so it isn't as efficient, but some people may find it more readable:
var result = from e in elements.Zip
(Enumerable.Range(0, elements.Count()), Tuple.Create)
where (string)e.Item1.Attribute("class") == "something"
select e.Item2;
It 'zips' the input collection with a generated sequence of numbers (with the same range as is the length of the collection). Then you can store the combined value either using the Tuple class (that's what I did) or you could use anonymous type.

Categories

Resources