How to GroupBy and Order by multiple fields with LINQ - c#

I have a DataTable (dtResult) with 4 fields, id, store, sku and qty. Unfortunately there are a lot of duplicates in the DataTable that I want to remove (qtys are diff, but other fields are the same).
I want to sort the DataTable by id asc, store asc, sku asc, and group by id, store and sku so I would have a list of unique records.
IDEALLY I would like to overwrite the existing DataTable with the results of the query, and qty can just be 0 for everything. I have the sort, and currently I'm putting it into a new DataTable:
var dtUniqueResults = dtResult.AsEnumerable()
.OrderBy(r => r.Field<string>("id"))
.ThenBy(r => r.Field<string>("store"))
.ThenBy(r => r.Field<string>("sku"))
.CopyToDataTable();
I don't understand how to group with LINQ. I think I need to add something like this, but it's not working.
var dtUniqueResults = dtResult.AsEnumerable()
.GroupBy(n => n.Field<string>("id"),
n => n.Field<string>("store"),
n => n.Field<string>("sku")
)
.OrderBy(r => r.Field<string>("id"))
.ThenBy(r => r.Field<string>("store"))
.ThenBy(r => r.Field<string>("sku"))
.CopyToDataTable();
I've read a lot of posts, and I see several ways of doing it. However it seems the two that are suggested the most are these, but they seem so different it just confuses me more.
GroupBy( x => new { x.Column1, x.Column2 })
AND
GroupBy(x=> new { x.Column1, x.Column2 }, (key, group) => new
{
Key1 = key.Column1,
Key2 = key.Column2,
Result = group.ToList()
});

If you need to filter out duplicates, try the following query:
var dtUniqueResults = dtResult.AsEnumerable()
.GroupBy(n => new
{
Id = n.Field<string>("id"),
Store = n.Field<string>("store"),
Sku = n.Field<string>("sku")
}
)
.SelectMany(g => g.Take(1)) // get from group only one record
.CopyToDataTable();

Related

Linq Select new with GroupBy and OrderBy

For the given data set, I want to return the unique rows for each OrderId that has the lowest number for Status, so the result would be:
I have a working query that does that:
var result = _dbContext.Orders
.GroupBy(s => s.OrderId)
.Select(group => group.OrderBy(x => x.Status).First()).ToList();
However, I would like to modify this query to only return three selected fields for each table row, rather than the dozens that exist. I know I need to add something like this:
.Select(group => new
{
OrderId = ???,
Status = ???,
Date = ???
}
But I am unable to add this to my existing query and have it still work. How can I do this?
You can try to do something like this:
var result = _dbContext.Orders
.GroupBy(s => s.OrderId)
.Select(group => group.OrderBy(x => x.Status).First())
.Select(order => new
{
OrderId = order.OrderId,
Status = order.Status,
Date = order.Date
})
.ToList();
In SQL you'd use:
SELECT OrderID,MIN(Status) as Status
FROM Orders
GROUP BY OrderID
A LINQ query is similar:
var query = context.Orders
.GroupBy(o=>o.OrderId)
.Select(g=> new {
OrderId=g.Key.OrderId,
Status=g.Min(o=>o.Status)
});
var results=query.ToList();

EF Core header-detail query optimization

I have a header-detail relation in my SQL Server database. I have around 10k headers, each of them having 1-1k details. And the number of unique elements is about 1k.
Elements [id]
1
2
3
Headers [id]
1
2
3
Details [id, header_id, element_id]
1 1 1
2 1 2
3 1 3
4 2 1
5 3 1
It's very easy to query a list of headers with their details with such structure:
var searchHeaderIds = new List<int>{1,2,3};
var headers = context.Headers
.Where(h => searchHeaderIds.Contains(h.Id))
.Include(h => h.Details)
.ToList();
But what I want to query is a list of elements (1-200) where every element has a list of headers it belongs to (something like an inversion). I can write it in C# as below:
var searchElementIds = new List<int>{1,2,3};
var headers = context.Details
.Where(d => searchElementIds.Contains(d.element_id))
.GroupBy(d => d.element_id)
.Select(g => new {
id = g.Key,
header_ids = g.Select(x => x.header_id) })
.ToList();
But I wonder, what will be the fastest way to do it using the power of SQL/EF?
UPD: I'm ready to use extra data structures, preprocess the data in the database, or do anything else to improve performance.
what about:
var searchElementIds = new List<int>{1,2,3};
var headers = (
from header in context.Headers
join detail in context.Details on header.id equals detail.header_id
where searchElementIds.Contains(detail.element_id)
select header).Distinct();
If you want instances of the Element class:
var headers =
context.Details
.Where(d => searchElementIds.Contains(d.element_id))
.GroupBy(d => d.element_id)
.Select(g => new Element
{
id = g.Key,
header_ids = g.Select(x => x.header_id
})
.ToList();
Don't cal ToList() in the middle of your query.
This is most optimal query in your case. It is closer to original post, but reduced number of retrieved fields for intermediate result:
var headers = context.Details
.Where(d => searchElementIds.Contains(d.element_id))
.Select(d => new { d.element_id, d.header_id })
.ToList() // we need this, EF do not support retrieving grouping detals
.GroupBy(d => d.element_id)
.Select(g => new Element
{
id = g.Key,
header_ids = g.Select(x => x.header_id).ToList()
})
.ToList();

Select all distinct last records of column1 and order by column2 in Linq (Not Working Accordingly)

So I have a table like this:
Now I want distinct ShortCode order by the ID descending. In other words, the distinct last records. Like this:
So I tried GroupBy like:
var data = db.ShortCodes.GroupBy(x => x.ShortCode).Select(x => x.FirstOrDefault()).OrderByDescending(s=> s.ID);
This gave me distinct records but not the last ones, nor ordered by ID descending:
Now I also tried like suggested here
var data = db.ShortCodeManager
.GroupBy(s => s. ShortCode)
.Select(g => g.First())
.OrderByDescending(s => s.ID);
This gave me the error The method 'First' can only be used as a final query operation. Consider using the method 'FirstOrDefault' in this instance instead.
So I modified to FirstOrDefault() like:
var data = db.ShortCodeManager
.GroupBy(s => s. ShortCode)
.Select(g => g.FirstOrDefault())
.OrderByDescending(s => s.ID);
This also gave me distinct records but not the last records:
So finally I tried like suggested here:
var data = db.ShortCodeManager.Where(a => a.ID > 0).GroupBy(x => x.ShortCode).OrderByDescending(grp => grp.Max(g => g.ID)).Select(a => a.FirstOrDefault());
Again, this gave me distinct records but not the last ones, nor ordered by ID descending:
So how am I to write the query to get the result I want in Linq? Also note, I need more of the distinct last records than ordering by ID descending. If anyone also knows how to write it in raw SQL it might be useful as well.
This LINQ query should work for your case:
var result = db.ShortCodeManager
.GroupBy(x => x.ShortCode)
.Select(gr => new { Id = gr.Max(g => g.Id), ShortCode = gr.Key})
.ToList();
EDIT:
Based on your comment it looks like you need to cast anonymous object result to ShortCodeManagerModel type and then pass it to your view. So, somethin like this:
var result = db.ShortCodeManager
.GroupBy(x => x.ShortCode)
.Select(gr => new { Id = gr.Max(g => g.Id), ShortCode = gr.Key})
.ToList();
var model = result
.Select(x => new ShortCodeManagerModel { Id = x.Id, ShortCode = x.ShortCode })
.ToList();
And then pass model to you view.

C# Linq - Refactoring ForEach Loop with Sub List

Am trying to refactor some data in order to display some charts.
I can't seem to figure out why using the following, it lists all the values at the top rather than being sequential like the source data.
var categories = VehicleSales.Select(v => v.name).Distinct().ToList();
var refactoredResults = new List<StackedColumnChart>();
foreach (var category in categories)
{
var subresult = VehicleSales.Where(x => x.vehicleType == category)
.GroupBy(x => x.vehicleType)
.Select(gcs => new StackedColumnChart
{
Category = category,
Values = gcs.Select(x => (int)x.data).DefaultIfEmpty(0).ToList()
}).ToList();
refactoredResults.AddRange(subresult);
}
Source Data:
Then the actual results and expected results:
Thanks in advance!
You can do that without loop and selecting a distinct values, just use GroupBy method and map each group to StackedColumnChart using Select
var refactoredResults = VehicleSales
.GroupBy(s => s.Category)
.Select(g => new StackedColumnChart
{
Category = g.Key,
Values = g.Select(s => s.Value).ToList()
})
.ToList();
If the original data is not sorted and you'll need to sort the values by week number, you can use OrderBy clause before selecting a values Values = g.OrderBy(s => s.WeekNumber).Select(s => s.Value).ToList()

linq group by and select multiple columns not in group by

I'm trying to select multiple columns not in a group by using linq - c#.
Using linq, I'm trying to group by ISNULL(fieldOne,''),ISNULL(fieldTo,'') and then select field_One, field_Two, field_Three for each group. So for each row that the group by would return, I want to see numerous rows.
So far I have the following, but can't seem to select all the needed columns.
var xy = tableQueryable.Where(
!string.IsNullOrEmpty(cust.field_One)
|| ! string.IsNullOrEmpty(ust.field_Two)
).GroupBy(cust=> new { field_One= cust.field_One ?? string.Empty, field_Tow = cust.field_Two ?? string.Empty}).Where(g=>g.Count()>1).AsQueryable();
Can somebody help pls?
You are pretty much there - all you are missing is a Select from the group:
var xy = tableQueryable
.Where(!string.IsNullOrEmpty(cust.first_name) || ! string.IsNullOrEmpty(ust.lastName))
.GroupBy(cust=> new { first_name = cust.first_name ?? string.Empty, last_name = cust.last_name ?? string.Empty})
.Where(g=>g.Count()>1)
.ToList() // Try to work around the cross-apply issue
.SelectMany(g => g.Select(cust => new {
Id = cust.Id
, cust.FirstName
, cust.LastName
, cust.RepId
}));
Select from each group does the projection of the fields that you want, while SelectMany dumps all the results into a flat list.
Would this work for you?
var groupsWithDuplicates = tableQueryable
.Where(c => !string.IsNullOrWhiteSpace(c.first_name) || !string.IsNullOrWhiteSpace(c.last_name))
.GroupBy(c => new { FirstName = c.first_name ?? "", LastName = c.last_name ?? "" })
.Where(group => group.Count() > 1) // Only keep groups with more than one item
.ToList();
var duplicates = groupsWithDuplicates
.SelectMany(g => g) // Flatten out groups into a single collection
.Select(c => new { c.first_name, c.last_name, c.customer_rep_id });
For me I have used following query to do the filter Customer and get the customer records group by the JobFunction. In my case the issue get resolved after adding the .AsEnumerable() after the where solve the problem.
var query = _context.Customer
.Where(x => x.JobTitle.ToUpper().Contains(searchText.ToUpper())).AsEnumerable()
.GroupBy(item => item.JobFunction,
(key, group) => new {
JobFunction = key,
CustomerRecords = group.ToList().Select(c => c).ToList()
})
.ToList();

Categories

Resources