Order Subgrouping in Linq Query - c#

I have a problem with a Linq query. I have a query formatted like the following which returns a result set that is grouped by the top level and is ordered by the top level strings alphabetically. I want to return a result set that is ordered by both the top level strings (so the group by clause is ordered alphabetically) and the sub level strings are also in alphabetical order (so each sub group is also alphabetical). What I'm expecting is something like the following:
A
A
B
C
B
A
B
C
My query is something like the following:
records.results
.Where(table => string.Compare(table, expected) == 0)
.OrderByDescending(table => table.name)
.GroupBy(table => table.name);
.OrderBy(group => group.Key);
I expect the OrderByDescending statement to change the ordering of the individual records in the groupby clause but it isn't effecting anything. I am receiving ordered results for the groups though. Any help is appreciated!

Your problem is that your final OrderBy statement is ordering the groups themselves. What you want to do is order the elements of each group. Try:
records.results
.Where(table => string.Compare(table, expected) == 0)
.OrderByDescending(table => table.name)
.GroupBy(table => table.name);
.Select(g => g.OrderByDescending(element => element.Name);
I'm not sure of the name of the property by which you want to order the groups is, but here I'm assuming it is Name.
The key here is to remember that IGrouping is itself an IEnumerable. See the documentation of IGrouping here and note that it implements IEnumerable, so we can call OrderByDescending on each group in your IEnumerable of IGroupings, which is the return type of Enumerable.GroupBy(documented here).
Since OrderByDescending will return an IEnumerable (instead of an IGrouping), this operation will lose the Key property on each group. If you need that, then you might want to simply select an anonymous type to keep the key. Replace the final Select with:
.Select(g => new { g.Key, elements = g.OrderByDescending(element => element.Name) });
Alternatively, you could write some logic that would intantiate an IGrouping here by following the directions here.

Related

Select item from one list based on item in another list in the same order

exhibits contain ids that are in a certain order. When I query another table to get the BMI ids based on the exhibit ids, the order is not the same. Instead of pulling the first document id in exhibit, I think it is pulling the first record in the database that has the same exhibit id, but I want it to pull the record in the database in the same order as the exhibits ids.
var exhibits = _context.ApExhibits.Where(x => x.CASE_ID == apDockets.CASE_ID)
.Where(x => x.EXHIBIT_NBR != null)
.Where(x => !documents617.Contains(x.DOC_ID))
.OrderBy(x => x.EXHIBIT_NBR)
.Select(x => x.DIM_ID).ToList();
if (exhibits.Count > 0)
{
var bmiIds =
_context.DocumentImages.Where(x => exhibits.Contains((int)x.DIM_ID))
.Select(x => (int)x.BMI_ID).ToList();
}
it seems like your first collection exhibits is ordered based on EXHIBIT_NBR whereas when you query the _context.DocumentImages you're not ordering it by the same property, hence you're going to receive results based on the order of the elements in the source sequence which in this case is _context.DocumentImages. Essentially, you're saying "given an element of the source sequence DocumentImages, search linearly within the exhibits collection and if there is an element which meets the given criteria then retain the element of the source sequence".
So let's say the first element from the source sequence DocumentImages to be passed into the Where clause has an equivalent id of an element from the collection exhibits but the element in exhibits is say at the 5th position, this will make the element of the source sequence the first element of the result list when we perform a ToList() eager operation on the methods whereas it should technically be at the 5th position of the result list given that the matching element from exhibits is also at the 5th position.
So in order to have elements that are in the same order as exhibits, one solution is to inner join the DocumentImages and exhibits collections which would be the equivalent of checking if one element in one collection is contained within another. Then we can order by the same property as you did with exhibits.
example with query syntax:
var bmiIds = (from e in _context.ApExhibits
join x in _context.DocumentImages on e.DIM_ID equals (int)x.DIM_ID
where exhibits.Contains((int)x.DIM_ID)
orderby e.EXHIBIT_NBR
select (int)x.BMI_ID).ToList();
example with fluent syntax:
var bmiIds = _context.ApExhibits
.Join(_context.DocumentImages,
e => e.DIM_ID,
x => (int)x.DIM_ID,
(e, x) => new { e.EXHIBIT_NBR, x.BMI_ID })
.Where(x => exhibits.Contains((int)x.DIM_ID))
.OrderBy(e => e.EXHIBIT_NBR)
.Select(x => (int)x.BMI_ID).ToList();

Group by Linq setting properties

I'm working on a groupby query using Linq, but I want to set the value for a new property in combination with another list. This is my code:
var result = list1.GroupBy(f => f.Name)
.ToList()
.Select(b => new Obj
{
ClientName = b.Name,
Status = (AnotherClass.List().Where(a=>a.state_id=b.????).First()).Status
})
I know I'm using a group by, but I'm not sure of how to access the value inside my bcollection to compare it with a.state_id.
This snippet:
Status = (AnotherClass.List().Where(a=>a.state_id=b.????).First()).Status
I've done that before but months ago I don't remember the syntax, when I put a dot behind b I have acces only to Key and the Linq Methods... What should be the syntax?`
Issue in your code is happening here:
a=>a.state_id=b.????
Why ?
Check type of b here, it would be IGrouping<TKey,TValue>, which is because, post GroupBy on an IEnumerable, you get result as IEnumerable<IGrouping<TKey,TValue>>
What does that mean?
Think of Grouping operation in the database, where when you GroupBy on a given Key, then remaining columns that are selected need an aggregation operation,since there could be more than one record per key and that needs to be represented
How it is represented in your code
Let's assume list1 has Type T objects
You grouped the data by Name property, which is part of Type T
There's no data projection so for a given key, it will aggregate the remaining data as IEnumerable<T>, as grouped values
Result is in the format IEnumerable<IGrouping<TK, TV>>, where TK is Name and TV represent IEnumerable<T>
Let's check out some code, break your original code in following parts
var result = list1.GroupBy(f => f.Name) - result will be of type IEnumerable<IGrouping<string,T>>, where list1 is IEnumerable<T>
On doing result.Select(b => ...), b is of type IGrouping<string,T>
Further you can run Linq queries on b, as follows:
b.Key, will give access to Name Key, there's no b.Value, for that your options could be following or any other relevant Linq operations:
a=>b.Any(x => a.state_id == x.state_id) or // Suuggests if an Id match in the Collection
a=>a.state_id == b.FirstOrDefault(x => x.state_id) //Selects First or default Value
Thus you can create a final result, from the IGrouping<string,T>, as per the logical requirement / use case

Retrieving non-duplicates from 2 Collections using LINQ

Background: I have two Collections of different types of objects with different name properties (both strings). Objects in Collection1 have a field called Name, objects in Collection2 have a field called Field.
I needed to compare these 2 properties, and get items from Collection1 where there is not a match in Collection2 based on that string property (Collection1 will always have a greater or equal number of items. All items should have a matching item by Name/Field in Collection2 when finished).
The question: I've found answers using Lists and they have helped me a little(for what it's worth, I'm using Collections). I did find this answer which appears to be working for me, however I would like to convert what I've done from query syntax (if that's what it's called?) to a LINQ query. See below:
//Query for results. This code is what I'm specifically trying to convert.
var result = (from item in Collection1
where !Collection2.Any(x => x.ColumnName == item.FieldName)
select item).ToList();
//** Remove items in result from Collection1**
//...
I'm really not at all familiar with either syntax (working on it), but I think I generally understand what this is doing. I'm struggling trying to convert this to LINQ syntax though and I'd like to learn both of these options rather than some sort of nested loop.
End goal after I remove the query results from Collection1: Collection1.Count == Collection2 and the following is true for each item in the collection: ItemFromCollection1.Name == SomeItemFromCollection2.Field (if that makes sense...)
You can convert this to LINQ methods like this:
var result = Collection1.Where(item => !Collection2.Any(x => x.ColumnName == item.FieldName))
.ToList();
Your first query is the opposite of what you asked for. It's finding records that don't have an equivalent. The following will return all records in Collection1 where there is an equivalent:
var results=Collection1.Where(c1=>!Collection2.Any(c2=>c2.Field==c1.Name));
Please note that this isn't the fastest approach, especially if there is a large number of records in collection2. You can find ways of speeding it up through HashSets or Lookups.
if you want to get a list of non duplicate values to be retained then do the following.
List<string> listNonDup = new List<String>{"6","1","2","4","6","5","1"};
var singles = listNonDup.GroupBy(n => n)
.Where(g => g.Count() == 1)
.Select(g => g.Key).ToList();
Yields: 2, 4, 5
if you want a list of all the duplicate values then you can do the opposite
var duplicatesxx = listNonDup.GroupBy(s => s)
.SelectMany(g => g.Skip(1)).ToList();

Entity Framework - Select distinct in

I have a table called Tag with a column called Label and a column called AuctionId. I also have an array of strings which are search terms. I want to write some Linq to Entities code which will give me a distinct list of AuctionIds where the the Label matches one of the search terms. Here is the pseudocode for this:
return a list of unique AuctionIds where Label is in searchTerms
How can this be done?
You can use Contains() on the list.
List<String> AuctionIDs = (from tagItem in Tags
where searchItems.Contains(tagItem.Label)
select tagItem.AutionID).Distinct().ToList();
Using Lambda notation for clarity, this breaks down to a number of functions in sequence as follows:
IEnumerable<Int32> DistinctIds = TagTable.Where(x => searchTerms.Contains(x.Label)).Select( x => x.AuctionId).Distinct()
Without going too far into the Lambda syntax, the key features here are:
.Where(x => searchTerms.Contains(x.Label)) - this will select out only rows where the searchTerms collection contains the Label value for that row
.Select( x => x.AuctionId) - return out only the integer AutionId values rather than the full record
.Distinct() - does just what it says on the tine
Hope this helps

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