Linq grouping by multiple columns - issue - c#

I seem to be running into an issue with what is probably a very simple task (forgive my newbie-ness with linq and c#).
I want to use linq to query an EF DB context. As part of this, I want to group by a few columns, then store these results into a list object.
I currently have this:
var Orders = orderSqlContext.OrderDetails
.GroupBy(x => new
{
x.OrderNumber,
x.OrderTotal
})
.Select(x => new
{
x.OrderNumber,
x.OrderTotal
}).ToList
I end up with errors on the two columns in the "select" portion above.
"OrderDetails does not contain a definition for OrderNumber and no extension method OrderNumber accepting a first argument of type IGrouping [...] could be found."
Basically, the OrderDetails context would normally return a list of orders with extra records for each line item on them. I want to also have a list of the distinct orders in that context.
Thank you!

You have to use the Key property of the group:
var Orders = orderSqlContext.OrderDetails
.GroupBy(x => new
{
x.OrderNumber,
x.OrderTotal
})
.Select(grp => new
{
grp.Key.OrderNumber,
grp.Key.OrderTotal
}).ToList();

it will be x.Key.OrderNumber and x.Key.OrderTotal. Read in the help how GroupBy works.

Related

How to sort something in LINQ based on many dates?

Hello this is a LINQ Query but it doesn't sort properly because four different dates are involved.
var EventReportRemarks = (from i in _context.pm_main_repz
.Include(a => a.PM_Evt_Cat)
.Include(b => b.department)
.Include(c => c.employees)
.Include(d => d.provncs)
where i.department.DepartmentName == "Finance"
orderby i.English_seen_by_executive_on descending
orderby i.Brief_seen_by_executive_on descending
orderby i.French_seen_by_executive_on descending
orderby i.Russian_seen_by_executive_on descending
select i).ToList();
All i want is that it should somehow combine the four dates and sort them in group not one by one.
For Example, at the moment it sorts all English Reports based on the date that executive has seen it, then Brief Report and So on.
But i want that it should check which one is seen first and so on. For example if the first report which is seen is French, then Brief, then English then Russian, so it should sort it accordingly.
Is it Possible??
You need to have them all in one column. The approach I would do, assuming that the value of the respective cells is null, when you don't want them to show up in the order by:
var EventReportRemarks = (from i in _context.pm_main_repz
.Include(a => a.PM_Evt_Cat)
.Include(b => b.department)
.Include(c => c.employees)
.Include(d => d.provncs)
where i.department.DepartmentName == "Finance"
select new
{
Date =
(
i.English_seen_by_executive_on != null ? i.English_seen_by_executive_on :
i.Brief_seen_by_executive_on != null ? i.Brief_seen_by_executive_on :
i.French_seen_by_executive_on != null ? i.French_seen_by_executive_on :
i.Russian_seen_by_executive_on
)
}).ToList().OrderBy(a => a.Date);
In the select clause you could add more columns if you whish.
Reference taken from here.
Why not just use .Min() or .Max() on the dates and then .OrderBy() or .OrderByDescending() based on that?
Logic is creating a new Enumerable (here, an array) with the 4 dates for the current line, and calculate the Max/Min of the 4 dates: this results in getting the latest/earliest of the 4. Then order the records based on this value.
var EventReportRemarks = (from i in _context.pm_main_repz
.Include(a => a.PM_Evt_Cat)
.Include(b => b.department)
.Include(c => c.employees)
.Include(d => d.provncs)
where i.department.DepartmentName == "Finance"
select i)
.OrderBy(i => new[]{
i.English_seen_by_executive_on,
i.Brief_seen_by_executive_on,
i.French_seen_by_executive_on,
i.Russian_seen_by_executive_on
}.Max())
.ToList();
Your problem is not a problem if you use method syntax for your LINQ query instead of query syntax.
var EventReportRemarks = _context.pm_main_repz
.Where(rep => rep.Department.DepartmentName == "Finance")
.OrderByDescending(rep => rep.English_seen_by_executive_on)
.ThenByDescending(rep => rep.Brief_seen_by_executive_on)
.ThenByDescending(rep => rep.French_seen_by_executive_on descending)
.ThenByDescending(rep => resp.Russian_seen_by_executive_on descending)
.Select(rep => ...);
Optimization
One of the slower parts of a database query is the transport of selected data from the DBMS to your local process. Hence it is wise to limit the transported data to values you actually plan to use.
You transport way more data than you need to.
For example. Every pm_main_repz (my, you do love to use easy identifiers for your items, don't you?), every pm_main_repz has zero or more Employees. Every Employees belongs to exactly one pm_main_repz using a foreign key like pm_main_repzId.
If you use include to transport pm_main_repz 4 with his 1000 Employees every Employee will have a pm_main_repzId with value 4. You'll transport this value 1001 times, while 1 time would have been enough
Always use Select to select data from the database and Select only the properties you actually plan to use. Only use Include if you plan to update the fetched objects
Consider using a proper Select where you only select the items that you actually plan to use:
.Select(rep => new
{
// only Select the rep properties you actually plan to use:
Id = rep.Id,
Name = rep.Name,
...
Employees = rep.Employees.Select(employee => new
{
// again: select only the properties you plan to use
Id = employee.Id,
Name = employee.Name,
// not needed: foreign key to pm_main_repz
// pm_main_repzId = rep.pm_main_repzId,
})
.ToList(),
Department = new
{
Id = rep.Department,
...
}
// etc for pm_evt_cat and provencs
});

LINQ: Select the Min and Max values from a collection on an entity after grouping

The goal is to get the first DateTime and Last DateTime from a collection on an Entity (Foreign Key). My Entity is an organization and my collection are Invoices. I'm grouping results since Organizations unfortunately are not Unique. I'm dealing with duplicate data and cannot assume my organizations are unique so I'm grouping by a Number field on my Entity.
I'm using .NET Core 2.1.2 with Entity Framework.
I'm trying to get the following query generated from LINQ:
SELECT MIN([organization].[Id]) AS Id, MIN([organization].[Name]) AS Name,
MIN([organization].[Number]) AS Number, MIN([invoice].[Date])
AS First, MAX([invoice].[Date]) AS Last
FROM [organization]
INNER JOIN [invoice] ON [invoice].[OrganizationId] = [organization].[Id]
GROUP BY [organization].[Number], [organization].[Name]
ORDER BY [organization].[Name]
However I have no idea how to get to write the LINQ query to get it to generate this result.
I got as far as:
await _context
.Organization
.Where(z => z.Invoices.Any())
.GroupBy(organization => new
{
organization.Number,
organization.Name
})
.Select(grouping => new
{
Id = grouping.Min(organization => organization.Id),
Name = grouping.Min(organization => organization.Name),
Number= grouping.Min(organization => organization.Number),
//First = ?,
//Last = ?
})
.OrderBy(z => z.Name)
.ToListAsync();
I have no clue how to write the LINQ query in such a way that it generates the above.
I have a couple questions still:
Are the Min statements for Id, Name and Number correct ways of getting the
first element in the grouping?
Do I need a join statement or is "WHERE EXISTS" better (this got generated before I changed the code)?
Does anyone know how to finish writing the LINQ statement? Because I have to get the first and last Date from the Invoices Collection on my Organization Entity:
organization.Invoices.Min(invoice => invoice.Date)
organization.Invoices.Max(invoice => invoice.Date)
Here is the trick.
To make inner join by using collection navigation property simple use SelectMany and project all primitive properties that you need later (this is important for the current EF Core query translator). Then perform the GroupBy and project the key properties / aggregates. Finally do the ordering.
So
var query = _context
.Organization
.SelectMany(organization => organization.Invoices, (organization, invoice) => new
{
organization.Id,
organization.Number,
organization.Name,
invoice.Date
})
.GroupBy(e => new
{
e.Number,
e.Name
})
.Select(g => new
{
Id = g.Min(e => e.Id),
Name = g.Key.Name,
Number = g.Key.Number,
First = g.Min(e => e.Date),
Last = g.Max(e => e.Date),
})
.OrderBy(e => e.Name);
is translated to
SELECT MIN([organization].[Id]) AS [Id], [organization].[Name], [organization].[Number],
MIN([organization.Invoice].[Date]) AS [First], MAX([organization.Invoice].[Date]) AS [Last]
FROM [Organization] AS [organization]
INNER JOIN [Invoice] AS [organization.Invoice] ON [organization].[Id] = [organization.Invoice].[OrganizationId]
GROUP BY [organization].[Number], [organization].[Name]
ORDER BY [organization].[Name]

LINQ To Entities returns list data in the wrong order [duplicate]

After running into some frustrations with my ASP.NET 5 API project I decided to rebuild it as an older WebApi 2 project. I'm trying to generate a list of collections of 2 strings (originally a list of a 2 element string array, now a list of list of strings) from a LINQ query.
Here is the pseudocode that worked as I wanted it to in the ASP.NET 5 project:
var testList = db.MyTable.Where(x => [WHERE Clauses]).Select(x => new string[] { x.Number.Trim(), x.Name.Trim() }).ToList();
The new project choked on the query citing it didn't like using string arrays for whatever reason (I'm assuming difference in EF 6 vs 7?). As a fix I instead had the query return a list of string lists but it mixes up the order of the "Number" and "Name" fields it returns, sometimes the Number is the first element and other times the Name is. Here is some new query code attempts I tried that all ended up with the jumbled element order:
var testList = db.MyTable.Where(x => [WHERE Clauses]).Select(x => new List<string> { x.Number.Trim(), x.Name.Trim() }).ToList();
var testList = db.MyTable.Where(x => [WHERE Clauses]).Select(x => (new string[2] { x.Number.Trim(), x.Name.Trim() }).ToList()).ToList();
var testList = db.MyTable.Where(x => [WHERE Clauses]).Select(x => new List<string>(new string[] { x.Number.Trim(), x.Name.Trim() })).ToList();
I realize there are a ton of different ways to get the end result I'm looking for but I'm hoping someone can help me understand why these list elements would be placed in the list in differing orders (sometimes Name then Number other times Number then Name), even when I generate a list based on a newly created array which preserved the order perfectly previously.
I'd be curious to see what SQL is generated for those queries and whether or not it changes when your result changes. Selecting a List to me seems like a pretty rare situation, so it's possible you have run into an edge-case (or at least into undefined behavior territory).
Since you're still essentially just selecting two columns, we can worry about how the data is structured after Entity Framework has finished it's work
db.MyTable.Where(x => [WHERE Clauses])
.Select(x => new { Number = x.Number.Trim(), Name = x.Name.Trim() })
.AsEnumerable() //Force EF to materialize the result here
.Select(x => new List<string> { x.Number, x.Name }) //Manipulate the result in memory
.ToList();

Order by user and then select max date

I have this LINQ query:
ArrayList arr = new ArrayList();
var data = conn.SCOT_DADOS.OrderByDescending(x => x.DATE)
.GroupBy(r => r.USER)
.ToList();
foreach (var item in data)
{
var itemdata = item.Where(r => r.DATE == item.Max(s => s.DATE));
var name = svc.GetUserName(itemdata.Select(r => r.USER).First().ToString());
var value = itemdata.Select(r => r.VALUE).First();
var date = itemdata.Select(r => r.DATE).First().ToString("dd/MM/yyyy HH:mm:ss");
arr.Add( new{ NAME = name, DATE = date, VALUE = value} );
}
This code will give me the latest result by DATE for each USER.
But the LINQ query is selecting all data from the user and then I'm getting the latest one in the foreach loop.
Is there any way to get only the last data in the LINQ query, so I don't have to take all the user data every time?
I have tried this:
var data = conn.SCOT_DADOS.OrderByDescending(x => x.DATE)
.GroupBy(r => r.USER)
.First()
.ToList();
And then treated item as an object, instead of running selects on it.
It gave me all the data for an individual user, which isn't what I want.
What can be done?
Edit 1:
I get this error if I try to swap OrderByDescending and GroupBy:
Error CS1061 'IGrouping' does not contain a
definition for 'DATE' and no extension method 'DATE' accepting a first
argument of type 'IGrouping' could be found (are
you missing a using directive or an assembly reference?)
Edit 2:
This is some sample data (the column names are not the same because I translated them for the question):
From the data presented, I'd have the results:
If the combination of the (USER, DATE) pair is unique (which seems to be the case when looking at the sample data), the requirement can be trimmed down to
return each record if there is no other record with the same USER and later DATE
which could be translated to the following LINQ query:
var result = conn.SCOT_DADOS
.Where(r => !conn.SCOT_DADOS.Any(r2 => r2.USER == r.USER && r2.Date > r.Date))
// end of Db Query
.AsEnumerable()
.Select(r => new
{
Name = svc.GetUserName(r.User),
Value = r.Value,
Date = r.Date.ToString("dd/MM/yyyy HH:mm:ss")
}).ToList();
I'm a bit confused but from your attempts with First() think you mean this:
conn.SCOT_DADOS.GroupBy(item => item.User)
.Select(grp => grp.OrderByDescending(i => t.Date).First());
This will retrieve for each User only the latest record of it
The reason only swapping the GroupBy and OrderByDescending isn't enough and that you need the Select is that once you grouped that data your enumerable is IEnumerable<IGrouping<User,YourType>>. Each IGrouping is actually a collection by itself so you need to Select only the 1 item you want from it.
Another way is to replace the Select with:
.SelectMany(grp => grp.OrderByDescending(i => t.Date).Take(1))
IMO the first is cleaner, but the second is in the case you need for each user N first items
On the query above you can also add what you have in the foreach loop:
conn.SCOT_DADOS.GroupBy(item => item.User)
.Select(grp => grp.OrderByDescending(i => t.Date).First())
.AsEnumerable()
.Select(item => new {
Name = svc.GetUserName(item.User),
Value = item.Value,
Date = item.Date.ToString("dd/MM/yyyy HH:mm:ss")
}).ToList();
The use of the AsEnumerable() is to invoke the query to be executed to the database before the last Select() which uses the GetUserName method that will not be known to the Oracle database
IMO representing the DateTime as string is not a good way..
Update - The error you get:
Oracle 11.2.0.3.0 does not support apply
It seems that as for this version of Oracle it does not support GroupBy with Select via linq. See Linq to Entities Group By (OUTER APPLY) “oracle 11.2.0.3.0 does not support apply”.
One answer there recommended to create a view in the database for this and then use linq to select over that view. That is what I'd go for
Try this
conn.SCOT_DADOS.GroupBy(x => x.User).Select(x => new
{
User = x.Key,
Date = list.Where(y => y.User == x.Key).Max(y => y.Date)
});

New string list shuffles order of list elements on initialization in LINQ

After running into some frustrations with my ASP.NET 5 API project I decided to rebuild it as an older WebApi 2 project. I'm trying to generate a list of collections of 2 strings (originally a list of a 2 element string array, now a list of list of strings) from a LINQ query.
Here is the pseudocode that worked as I wanted it to in the ASP.NET 5 project:
var testList = db.MyTable.Where(x => [WHERE Clauses]).Select(x => new string[] { x.Number.Trim(), x.Name.Trim() }).ToList();
The new project choked on the query citing it didn't like using string arrays for whatever reason (I'm assuming difference in EF 6 vs 7?). As a fix I instead had the query return a list of string lists but it mixes up the order of the "Number" and "Name" fields it returns, sometimes the Number is the first element and other times the Name is. Here is some new query code attempts I tried that all ended up with the jumbled element order:
var testList = db.MyTable.Where(x => [WHERE Clauses]).Select(x => new List<string> { x.Number.Trim(), x.Name.Trim() }).ToList();
var testList = db.MyTable.Where(x => [WHERE Clauses]).Select(x => (new string[2] { x.Number.Trim(), x.Name.Trim() }).ToList()).ToList();
var testList = db.MyTable.Where(x => [WHERE Clauses]).Select(x => new List<string>(new string[] { x.Number.Trim(), x.Name.Trim() })).ToList();
I realize there are a ton of different ways to get the end result I'm looking for but I'm hoping someone can help me understand why these list elements would be placed in the list in differing orders (sometimes Name then Number other times Number then Name), even when I generate a list based on a newly created array which preserved the order perfectly previously.
I'd be curious to see what SQL is generated for those queries and whether or not it changes when your result changes. Selecting a List to me seems like a pretty rare situation, so it's possible you have run into an edge-case (or at least into undefined behavior territory).
Since you're still essentially just selecting two columns, we can worry about how the data is structured after Entity Framework has finished it's work
db.MyTable.Where(x => [WHERE Clauses])
.Select(x => new { Number = x.Number.Trim(), Name = x.Name.Trim() })
.AsEnumerable() //Force EF to materialize the result here
.Select(x => new List<string> { x.Number, x.Name }) //Manipulate the result in memory
.ToList();

Categories