Linq Group selection - c#

I need to filter these results to only show the first and last row from each group. I've tried a couple of approaches with no luck. Below is the Linq query and the results.
Linq Query:
var currentAndHistoricalOnly = ViewCustomerGLAndPurchaseRecord
.Where(g=>g.CashierDate > new Datetime(2012,10,01)
&& g.CashierDate < new DateTime(2012,12,01))
.GroupBy(x => x.TransactionId)
.OrderByDescending(d => d.First().CashierDate);
Remember, I'm trying to keep the same grouping but filter(remove records) each group and take only the first record and the last record of each group.
So instead of:
Group 1:
Record 1
Record 2
Record 3
Record 4
Record 5
Group 2:
Record 1
Record 2
Record 3
Record 4
I need:
Group 1:
Record 1
Record 5
Group 2:
Record 1
Record 4

Maybe something like this:
var currentAndHistoricalOnly = ViewCustomerGLAndPurchaseRecord
.Where(g=>g.CashierDate > new DateTime(2012,10,01) && g.CashierDate < new DateTime(2012,12,01))
.GroupBy(x => x.TransactionId)
.Select(g=> new {
Last = g.OrderByDescending(c=>c.CashierDate).FirstOrDefault(),
First = g.OrderBy(c=>c.CashierDate).FirstOrDefault(),
})
.ToList();

You can use the last and first methods

Try this instead:
var list = ViewCustomerGLAndPurchaseRecord
.Where(g=>g.CashierDate > new DateTime(2012,10,01)
&& g.CashierDate < new DateTime(2012,12,01))
.GroupBy(x => x.TransactionId)
.OrderByDescending(d => d.First().CashierDate)
.Select(d =>
{
var result = new List<ViewCustomerGLAndPurchaseRecord>();
result.Add(d.FirstOrDefault());
result.Add(d.LastOrDefault());
return result;
});
If you are using Entity Framework, Use AsEnumerable() before select.
Hope this will help !!

Related

How can I get build a query based on array of IDs using EF

I'm trying to build a query selecting all records containing IDs which are stored in the list using that code:
var assistsIds = _context.Assistances.Where(c => c.IdUser == user.IdUser)
.Select(x => x.Owner.IdOwner).ToList();
Then I'm going through all the list elements to get a query:
var query = _context.Accounts.Where(_ => _.IsDeleted != 1);
foreach(var assist in assistsIds)
{
query = query.Where(_ => _.IdOwner == assist);
}
The result is that I'm getting something like this:
SELECT * FROM Accounts WHERE IdOwner = 1 AND IdOwner = 2 ...etc
Instead of:
SELECT * FROM Accounts WHERE IdOwner = 1 OR IdOwner = 2 ... etc
Is there a way to apply OR operator, or maybe there is some other way to achieve that?
You could use Contains:
var query = _context.Accounts
.Where(_ => _.IsDeleted != 1 && assistsIds.Contains(_.IdOwner));
This will return all records which match an Id in the assistsIds list.

Select one of each matching results from group last record from date

I have multiple customers that are a part of a group designated by a group id.
I would like to retrieve 1 record from a related table for each of the matching group members (last record before a certain date).
Currently I query for a list of group members then for each member i run another query to retrieve last record from a date.
I would like to do this with one query since i can pull up the associated table records using group id - however this returns all the records associated to group (bad).
If i use first or default i only get results for first group found.
I want 1 record from each group member.
My Code (returns all associated records of group members):
List<Record> rs = (from x in db.Records where (x.Customer.Group == udcg && x.CloseDate < date && x.CloseDate < earlyDate) orderby x.CloseDate descending select x).ToList();
But i just want one from each instead of all.
Code I use now:
var custs = (from x in db.Customers where (x.group == udcg) select new { x.CustomerID }).ToList();
expected = custs.Count();
foreach (var cust in custs)
{
Record br = (from x in db.Records where (x.Customer.CustomerID == cust.CustomerID && x.CloseDate < date && x.CloseDate < earlyDate)) orderby x.CloseDate descending select x).FirstOrDefault();
if (br != null)
{
total = (double)br.BillTotal;
cnt++;
}
}
I think this could work
db.Customers
.Where(c => c.group == udcg)
.Select(c => db.Records
.Where(r => r.Customer.CustomerID == c.CustomerID)
.Where(r => r.CloseDate < date)
.Where(r => r.CloseDate > date.AddMonths(-2))
.OrderByDescending(r => r.CloseDate)
.FirstOrDefault())
.Where(r => r != null)
It is translated into one sql query. That means it uses one roundtrip to the server. That could be quite a big difference in performace when compared to the foreach loop. If you look at the generated sql, it would be something like
SELECT some columns
FROM Customers
OUTER APPLY (
SELECT TOP (1) some columns
FROM Records
WHERE some conditions
ORDER BY CloseData DESC
)
In terms of performace of the query itself, I would not expect problems here, sql server should not have problems optimizing this form (compared to other ways you could write this query).
Please try this one, evaluate records list.
DateTime certain_date = new DateTime(2018, 11, 1);
List<Record> records = new List<Record>();
var query = records.GroupBy(x => x.Customer.Group).Select(g => new { Group = g.Key, LastRecordBeforeCertainDate = g.Where(l => l.CloseDate < certain_date).OrderByDescending(l => l.CloseDate).FirstOrDefault() });

Getting the count of most repeated records in Linq

I am working on an application in which I have to store play history of a song in the data table. I have a table named PlayHistory which has four columns.
Id | SoundRecordingId(FK) | UserId(FK) | DateTime
Now i have to implement a query that will return the songs that are in trending phase i.e. being mostly played. I have written the following query in sql server that returns me data somehow closer to what I want.
select COUNT(*) as High,SoundRecordingId
from PlayHistory
where DateTime >= GETDATE()-30
group by SoundRecordingId
Having COUNT(*) > 1
order by SoundRecordingId desc
It returned me following data:
High SoundRecordingId
2 5
2 3
Which means Song with Ids 5 and 3 were played the most number of times i.e.2
How can I implement this through Linq in c#.
I have done this so far:
DateTime d = DateTime.Now;
var monthBefore = d.AddMonths(-1);
var list =
_db.PlayHistories
.OrderByDescending(x=>x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId)
.Take(20)
.ToList();
It returns me list of whole table with the count of SoundRecording objects but i want just count of the most repeated records.
Thanks
There is an overload of the .GroupBy method which will solve your problem.
DateTime d = DateTime.Now;
var monthBefore = d.AddMonths(-1);
var list =
_db.PlayHistories
.OrderByDescending(x=>x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId, (key,values) => new {SoundRecordingID=key, High=values.count()})
.Take(20)
.ToList();
I have simply added the result selector to the GroupBy method call here which does the same transformation you have written in your SQL.
The method overload in question is documented here
To go further into your problem, you will probably want to do another OrderByDescending to get your results in popularity order. To match the SQL statement you also have to filter for only counts > 1.
DateTime d = DateTime.Now;
var monthBefore = d.AddMonths(-1);
var list =
_db.PlayHistories
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId, (key,values) => new {SoundRecordingID=key, High=values.count()})
.Where(x=>x.High>1)
.OrderByDescending(x=>x.High)
.ToList();
I like the 'linq' syntax it's similar to SQL
var query = from history in _db.PlayHistories
where history.DateTime >= monthBefore
group history by history.SoundRecordingId into historyGroup
where historyGroup.Count() > 1
orderby historyGroup.Key
select new { High = historyGroup.Count(), SoundRecordingId = historyGroup.Key };
var data = query.Take(20).ToList();
You´re allmost done. Just order your list by the count and take the first:
var max =
_db.PlayHistories
.OrderByDescending(x=>x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId)
.OrderByDescending(x => x.Count())
.First();
This gives you a single key-value-pair where the Key is your SoundRecordingId and the value is the number of its occurences in your input-list.
EDIT: To get all records with that amount chose this instead:
var grouped =
_db.PlayHistories
.OrderByDescending(x => x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x => x.SoundRecordingId)
.Select(x => new { Id = x.Key, Count = x.Count() }
.OrderByDescending(x => x.Count)
.ToList();
var maxCount = grouped.First().Count;
var result = grouped.Where(x => x.Count == maxCount);
This solves the problem by giving you what you asked for. Your query in LINQ, returning just the play counts.
var list = _db.PlayHistories.Where(x => x.DateTimeProp > (DateTime.Now).AddMonths(-1))
.OrderByDescending(y => y.SoundRecordingId.Count())
.ThenBy(z => z.SoundRecordingId)
.Select(xx => xx.SoundRecordingId).Take(20).ToList();

LINQ: How to get the Max Id with a group by clause?

I am looking for a way in LINQ to get a max Id record by using 'Group By' clause
Consider the following Sample data
Table: ProcessAud
ProcessSeq ProjectSeq ProjectValue Active
11 1 50000 Y
12 1 10000 Y
13 2 70000 Y
14 2 90000 Y
In which I want to get two records as a list such that is second and fourth
records (i.e) ProcessSeq 12 and 14. And I tried it like following
var ProcessAudList = ProcessAudService.FilterBy(x => x.Active == "Y"
).GroupBy(x => x.ProjectSeq).Max().ToList();
It is not working properly, So how to do it in LINQ. Please anybody help.
You want to pick top record from each group.
var ProcessAudList = ProcessAudService.Where(x => x.Active == "Y")
.GroupBy(x => x.ProjectSeq, (key,g)=>g.OrderByDescending(e=>e.ProjectValue).First());
Check demo code
When you use GroupBy extension, method will return you IGrouping instance and you should query IGrouping instance like below;
var ProcessAudList = collection.Where(x => x.Active == "Y").GroupBy(x => x.ProjectSeq).Select(x => x.OrderByDescending(a => a.ProcessSeq).FirstOrDefault()).ToList();
Hope this helps
You're most of the way there, but Max is the wrong term to use.
Each IGrouping is an IEnumerable (or IQueryable) sequence of its own, so you can use OrderBy and First clauses to get the answer you need:
var ProcessAudList = ProcessAudService
.FilterBy(x => x.Active == "Y")
.GroupBy(x => x.ProjectSeq)
.Select(grp => grp.OrderByDescending(x => x.ProcessSeq).First())
.ToList();
The Select clause will process each of the groups, order the groups descending by ProcessSeq and select the first one. For the data you provided this will select the rows with ProcessSeq equal to 12 and 14.
With this code you can get all max id in foreach
var res = from pa in ProcessAud
group Cm by pa.ProjectSeq into Cm1
select new
{
_max = Cm1.Max(x => x.ProcessSeq)
};
foreach (var item in res)
{
//item._max have biggest id in group
}

Determine Duplicate data using LINQ to EF

I have a dataset that i want to groupby to determine duplicate data.
Example i have a dataset that looks like this.
|id | Number | ContactID
1 1234 5
2 9873 6
3 1234 7
4 9873 6
Now i want to select data that has more than one occurrence of Number but only if the ContactID is not the same.
So basically return
| Number | Count |
1234 2
Any help would be appreciated using LINQ to EF, thanks.
Update:
All thanks to #DrCopyPaste, as he told me that I misunderstood your problem. Here is the correct solution:-
var result = from c in db.list
group c by c.Number into g
let count = g.GroupBy(x => x.ContactID).Where(x => x.Count() == 1).Count()
where count != 0
select new
{
Number = g.Key,
Count = count
};
Sample Fiddle.
This query avoids making a custom IEqualityComparer as if I remember correctly don't think they play well with EF.
var results = data.GroupBy(number => number.Number)
.Where(number => number.Count() > 1)
.Select(number => new
{
Number = number.Key,
Count = number.GroupBy(contactId => contactId.ContactId).Count(x => x.Count() == 1)
})
.Where(x => x.Count > 0).ToList();
Fiddle
It does an initial GroupBy to get all Numbers that are duplicated. It then selects a new type that contains the number and a second GroupBy that groups by ContactId then counts all groups with exactly one entry. Then it takes all results whose count is greater than zero.
Have not testing it against EF, but the query uses only standard Linq operators so EF shouldn't have any issues translating it.
Another way of doing this(using 1 level of grouping):
var results = data
.Where(x => data.Any(y => y.Id != x.Id && y.Number == x.Number && y.ContactId != x.ContactId))
.GroupBy(x => x.Number)
.Select(grp => new { Number = grp.Key, Count = grp.Count() })
.ToList();
Fiddle

Categories