Can somebody explain me the following LINQ code? - c#

I am having trouble interpreting code from someone else.
I know the behavior, but I don't understand how.
Behavior:
driverRemark has a sequence number and can be linked to a group.
The group that contains the remark with the lowest seq nr should be printed out first. and so on.
If 2 remarks have the lowest seq nr, the lowest group should be printed first. except when its null, null should be printed last.
Can somebody explain me what is happening here?
var groupedDriverRemarks = driverRemarks
.GroupBy(dr => dr.RemarkGroup)
.OrderBy(g => g.Key == null)
.ThenBy(g => g.Key)
.OrderBy(gr => gr.Min(dr => dr.SeqNr))
.SelectMany(g => g.OrderBy(dr => dr.SeqNr))
.ToList();
return groupedDriverRemarks;
I have no clue how this function operates. It does work, but no clue why.
I tried searching all the different LINQ methods and try to translate it. But without any result.

Here is a commented version of the code:
var groupedDriverRemarks = driverRemarks
.GroupBy(dr => dr.RemarkGroup) // group driver remarks by RemarkGroup
.OrderBy(g => g.Key == null) // order groups so null RemarkGroup is last
.ThenBy(g => g.Key) // order non-null remark groups by RemarkGroup
.OrderBy(gr => gr.Min(dr => dr.SeqNr))
// re-order remark groups by minimum of SeqNr;
// since previous ordering is preserved, any duplicate minimum SeqNr will be ordered by RemarkGroup with null last still
.SelectMany(
g => g.OrderBy(dr => dr.SeqNr) // sort contents of each group in SeqNr order
) // flatten SeqNr sorted groups into single stream
.ToList(); // convert ordered driver remarks to a List<T>
The use of multiple sorts is not an efficient way to handle sub-sorts, the ThenBy method is intended for this, so you have one sort that works correctly. Here is an improved version; it uses one sort before grouping to pre-order all the driver remarks so the final result is properly sorted after grouping.
var orderedDriverRemarks =
driverRemarks
.OrderBy(dr => dr.SeqNr) // order the driver remarks by SeqNr
.OrderBy(dr => dr.RemarkGroup == null) // then order duplicate SeqNr so the null RemarkGroup remarks are last
.ThenBy(dr => dr.RemarkGroup) // then order by RemarkGroup for the non-null remarks with duplicate SeqNr
.GroupBy(dr => dr.RemarkGroup) // group driver remarks by RemarkGroup
// since GroupBy is order preserving, the groups will be ordered by their first SeqNr
// which is the minimum SeqNr in each group, then duplicate min SeqNrs will be ordered
// by RemarkGroup with null last
// in addition, the contents of each group will be ordered by SeqNr
.SelectMany(g => g) // flatten groups into single stream
.ToList(); // convert ordered driver remarks to a List<T>

Related

remove a few items (Id) from results

im trying to remove a few items from a query results using linqCore but its not removing them
code& results
IQueryable<int> postOn = //Results=9(Id)
_context.QuettaOffers.Where(d => d.SiteUserId == LoguserId).Select(s => s.QuettaReqId);
//Get all request that relevant to user without the one he already post on
IOrderedEnumerable<QuettaReq> listOfR = //Results= 9,11 (Id) should be ..11(Id)
//IQueryable<QuettaReq> listOfR
_context.Quetta.Include(q => q.Category)
.Where(d => d.OfferDate > DateTime.Now && d.CatId == suplayerCat)
.ToList()
//.ToList()
.SkipWhile(a => a.Id.Equals(postOn))
.OrderBy(x => x.Id);
// .TakeWhile(a => a.Id != postOn);
There are several issues with your code.
First, SkipWhile / TakeWhile are useless for unordered sequences, which are normally the result of EF queries except they include explicit OrderBy. The standard and more appropriate method for filtering is Where.
Second, a.Id.Equals(postOn) resolves to object.Equals, and since a.Id is int and postOn is IQueryable<int>, it always evaluates to false.
What you really need is additional Where condition based on !Contains. It could be && to the current Where or just separate Where (these constructs are treated one and the same way):
_context.Quetta.Include(q => q.Category)
.Where(d => d.OfferDate > DateTime.Now && d.CatId == suplayerCat)
.Where(q => !postOn.Contains(q.Id))
The additional benefit would be that the filtering will happen server side.
SkipWhile will only skip items in the beginning of the IEnumerable<T>. Once that condition isn't met it will happily take the rest of the elements. Other elements that later match it down the road won't be skipped.
instead of SkipWhile you can use Except
var result = QueryResult.Except(a => a.Id.Equals(Id here));

Remove every first element of grouped collection

I have a collection of elements and some of these elements are duplicating. I need to extract all records but only the first record if the record is one of a duplicate set.
I was able to group the elements and find all elements that have duplicates, but how to remove every first element of a group?
var records =
dbContext.Competitors
.GroupBy(x => x.Email)
.Select(x => new { Properties = x,
Count = x.Key.Count() })
.Where(x => x.Count > 1)
.ToList();
EDIT: Seems like it's impossible to accomplish this task with EF, because it fails to translate the desired linq expression to SQL. I'll be happy if someone offer different approach.
To exclude the first record from each email-address group with more than one entry, you could do this:
var records = dbContext.Competitors
.GroupBy(x => x.Email)
.SelectMany(x => (x.Count() == 1) ? x : x.OrderBy(t=>t).Skip(1))
.ToList();
This is the logic :
Group by a property > Select every Group > (Possibly) Sort that > Skip first one
This can be turned into some linq code like this :
//use SelectMany to flat the array
var x = list.GroupBy(g => g.Key).Select(grp => grp.Skip(1)).SelectMany(i => i);

LINQ group by and order by in C#

I need to convert my city list into group by state and order by city within it.
I tried below one but not able to get it right. Would appreciate any help on this.
cities.GroupBy(g => g.state).Select(o => o.OrderBy(c => c.cityname));
Try below code
cities.GroupBy(g => g.state)
.Select(o =>new {
State = o.Key,
Cities = o.OrderBy(c => c.cityname).Tolist()})
.Tolist();
cits.OrderBy(d => d.cityname).GroupBy(d => d.state).SelectMany(g => g).ToList();
1 - Order by cityname first.
2 - Then group them according to state. Since you order first, groups are still ordered with respect to cityname property.
3 - Convert to single list. Otherwise, you will end up with list of groups.
Should work. I also advice using camel case notation for naming your variables.
The ToLookup function may give you what you need.
cities.ToLookup(c => c.state, c => c.city);
This will create an IGrouping<string, string> where you can iterate through the Key values (states) and operate on a set of city values.
To sort it first, just do cities.OrderBy(c => c.state).ThenBy(c => c.city).
Do the orderby first:
cities.OrderBy(c=>c.cityname).GroupBy (c => c.state);
You might want to order the states to so.
cities.OrderBy(c=>c.cityname).GroupBy (c => c.state).OrderBy (g => g.Key);

Counting grouped data with Linq to Sql

I have a database of documents in an array, each with an owner and a document type, and I'm trying to get a list of the 5 most common document types for a specific user.
var docTypes = _documentRepository.GetAll()
.Where(x => x.Owner.Id == LoggedInUser.Id)
.GroupBy(x => x.DocumentType.Id);
This returns all the documents belonging to a specific owner and grouped as I need them, I now need a way to extract the ids of the most common document types. I'm not too familiar with Linq to Sql, so any help would be great.
This would order the groups by count descending and then take the top 5 of them, you could adapt to another number or completely take out the Take() if its not needed in your case:
var mostCommon = docTypes.OrderByDescending( x => x.Count()).Take(5);
To just select the top document keys:
var mostCommonDocTypes = docTypes.OrderByDescending( x => x.Count())
.Select( x=> x.Key)
.Take(5);
You can also of course combine this with your original query by appending/chaining it, just separated for clarity in this answer.
Using the Select you can get the value from the Key of the Grouping (the Id) and then a count of each item in the grouping.
var docTypes = _documentRepository.GetAll()
.Where(x => x.Owner.Id == LoggedInUser.Id)
.GroupBy(x => x.DocumentType.Id)
.Select(groupingById=>
new
{
Id = groupingById.Key,
Count = groupingById.Count(),
})
.OrderByDescending(x => x.Count);

Sorting an ArrayList

Based on my previous question, I've trying now to have them in the following order using the same approach, OrderByDescending and ThenBy
Original (can be in any random order):
1:1
0:0
0:1
2:1
1:0
2:0
Output
2:0
1:0
0:0
2:1
1:1
0:1
as you can see, a is descending, and b being ascending. But I'm still not getting the right sort. Any ideas why? Thanks
Think to what you would do manually:
First you must sort the values by the 2nd part in ascending order
Then you must sort values having the same 2nd part, using the 1st part in descending order
Translated in LINQ it's pretty the same:
var sorted = arrayList
.Cast<string>()
.Select(x => x.Split(':'))
.OrderBy(x => x[1])
.ThenByDescending(x => x[0])
.Select(x => x[0] + ":" + x[1]);
To clarify a bit more, ThenBy/ThenByDescending methods are used to sort elements that are equal in the previous OrderBy/OrderByDescending, hence the code :)
arrayList.ToList().Select(i => { var split = i.Split(":".ToArray(),2));
return new { a = Int32.Parse(split[0]),
b = Int32.Parse(split[1}) };
})
.OrderByDescending(i => i.a)
.ThenBy(i => i.b)
From your question it is not clear whether you want the order-by's reversed (just swap them).
Work from there, perhaps rejoining
.Select(i => String.Format("{0}:{1}", i.a, i.b));
Good luck

Categories