Order by user and then select max date - c#

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)
});

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]

Convert SQL query with multiple GroupBy columns to LINQ

SELECT
[TimeStampDate]
,[User]
,count(*) as [Usage]
FROM [EFDP_Dev].[Admin].[AuditLog]
WHERE [target] = '995fc819-954a-49af-b056-387e11a8875d'
GROUP BY [Target], [User] ,[TimeStampDate]
ORDER BY [Target]
My database table has the columns User, TimeStampDate, and Target (which is a GUID).
I want to retrieve all items for each date for each user and display count of entries.
The above SQL query works. How can I convert it into LINQ to SQL? Am using EF 6.1 and my entity class in C# has all the above columns.
Create Filter basically returns an IQueryable of the entire AuditLogSet :
using (var filter = auditLogRepository.CreateFilter())
{
var query = filter.All
.Where(it => it.Target == '995fc819-954a-49af-b056-387e11a8875d')
.GroupBy(i => i.Target, i => i.User, i => i.TimeStamp);
audits = query.ToList();
}
Am not being allowed to group by on 3 columns in LINQ and I am also not sure how to select like the above SQL query with count. Fairly new to LINQ.
You need to specify the group by columns in an anonymous type like this:-
var query = filter.All
.Where(it => it.Target == '995fc819-954a-49af-b056-387e11a8875d')
.GroupBy(x => new { x.User, x.TimeStampDate })
.Select(x => new
{
TimeStampDate= x.Key.TimeStampDate,
User = x.Key.User,
Usage = x.Count()
}).ToList();
Many people find query syntax simpler and easier to read (this might not be the case, I don't know), here's the query syntax version anyway.
var res=(from it in filter.All
where it.Target=="995fc819-954a-49af-b056-387e11a8875d"
group it by new {it.Target, it.User, it.TimeStampDate} into g
orderby g.Key.Target
select new
{
TimeStampDate= g.Key.TimeStampDate,
User=g.Key.User,
Usage=g.Count()
});
EDIT: By the way you don't need to group by Target neither OrderBy, since is already filtered, I'm leaving the exact translation of the query though.
To use GroupBy you need to create an anonymous object like this:
filter.All
.Where(it => it.Target == '995fc819-954a-49af-b056-387e11a8875d')
.GroupBy(i => new { i.Target, i.User, i.TimeStamp });
It is unnecessary to group by target in your original SQL.
filter.All.Where( d => d.Target == "995fc819-954a-49af-b056-387e11a8875d")
.GroupBy(d => new {d.User ,d.TimeStampDate} )
.Select(d => new {
User = d.Key.User,
TimeStampDate = d.Key.TimeStampDate,
Usage = d.Count()
} );

Trying to get the first record for each MemID in this LINQ Query

I have a LINQ query, which is working, as below. The only problem is that sometimes I get repeating MEMIds. How can I get only the first MemID from this query in a single database trip?
I am using SQL Server 2008 R2 as my backend database, and C# as the programming language.
var query = (from m in e.Memberships
where m.MEMID != null
&& (SqlFunctions.StringConvert((double)m.MEMID).Contains(memIdOrName)
|| m.NAME.Contains(memIdOrName))
select new {
m.MEMID,
NAME = m.NAME.TrimEnd(),
m.CITY,
m.STATE,
m.SYSTEMID,
SYSTEMNAME = m.SYSTEMNAME.TrimEnd()
})
.Distinct()
.OrderBy(s => s.NAME)
.ThenBy(s => s.CompanyID)
.ThenBy(s => s.CITY)
.ThenBy(s => s.MEMID);
var a = query.Skip(startRowIndex).Take(maximumRows).ToList();
Group on that value and then select out just one item from that group. If you don't care which, you can just grab the first. If you want a particular one, then you can re-order them before taking the first item.
So replace Distinct with;
//everything before `Distinct`
.GroupBy(s => s.MEMID)
.Select(group => group.FirstOrDefault())//or some other query to get one item in the group
//rest of your query

Linq select with filtering not working

I'm trying to select one field last record in filtered database (this is different than last inserted record). I tried with following code in controller but instead of field value, i'm getting "true" or "false", depending on if there's results after filtering or not.
List<Pozicije> poz = new List<Pozicije>();
poz = db.Pozicijes.Where(p => p.grupa == grupa)
.OrderBy(p => p.sifra_pozicije).ToList();
string pos = poz.Select(p => p.sifra_pozicije.Contains(s)).LastOrDefault().ToString();
can someone point me how to get value i need instead?
Try this instead. I've combined both parts of your query into one.
var pos =
Convert.ToString(db.Pozicijes.Where(p => p.grupa == grupa
&& p.sifra_pozicije.Contains(s))
.OrderByDescending(p => p.sifra_pozicije)
.Select(p => p.sifra_pozicije)
.FirstOrDefault());
If it doesn't work, you may need to tell us what types s and sifra_pozicije are.
LastOrDefault is not supported with LINQ to Entities/LINQ TO SQL. You need to do OrderByDescending and then get First record. Like:
string pos = db.Pozicijes.Where(p => p.grupa == grupa && p.sifra_pozicije.Contains(s)))
.OrderByDescending(p=> p.sifra_pozicije)
.Select(r=> r.sifra_pozicije)
.First();

Categories