Gettiing last items using linq query - c#

I have a list that get from database using entity framework.
var list = context.Items;
list result is like this.
var list = new List<Item>{
new Item { id=1, operation="write", date="23.03.2018 08:25:45" },
new Item { id=1, operation="read", date="23.03.2018 09:40:15" },
new Item { id=1, operation="read", date="23.03.2018 10:15:17" },
new Item { id=1, operation="read", date="23.03.2018 11:46:39" }
}
I want to minify this list by operation and last date.
var min = new List<Item>{
new Item { id=1, operation="write", date="23.03.2018 08:25:45" },
new Item { id=1, operation="read", date="23.03.2018 11:46:39" }
}
I am getting last 'write' operation and last 'read' item.

I have used the concept of numbering the rows based on the date, and then filtering them. Something like:
var groups = list.GroupBy(x => x.operation)
.SelectMany(g =>
g.OrderByDescending(i => i.date).Select((j, i) => new { j.id, j.operation,j.date, rn = i + 1 })
);
var data = groups.Where(i => i.rn == 1).ToList();

You can achieve it with the help of following code
var result = list.GroupBy(x => x.operation)
.Select(x => x.OrderByDescending(y => Convert.ToDateTime(y.date)).FirstOrDefault())
.ToList();

Seems like you need distinct value form the list and then you need to get first item with latest date
List<Item> list = context.Items
.GroupBy(a => a.operation)
.Select(g => g.OrderByDescending(p => Convert.ToDateTime(p.date)).First())
.ToList();
you need to perform group by on operation once its done you should take first item from each group but to take latest by date you need to perform descending on date which will make latest date record first recoed and than you just need to apply first function on it.

You can try the following. Just group by with operation and select the last date in each group.
var lastItems = list.GroupBy(p => p.operation).Select(grp => new
{
grp.Key,
lastOperation = grp.OrderByDescending(p => Convert.ToDateTime(p.date)).First()
}).Select(p=> p.lastOperation);
However, if your date field contains different time formats it may fail to convert to DateTime.
And you need to make sure to convert date field to DateTime otherwise you may get different ordering by string.
Edit: The following does the same thing with less typing:
var lastItems = list.GroupBy(p => p.operation).Select(grp =>
grp.OrderByDescending(p => Convert.ToDateTime(p.date)).First());

Since Max is (much) faster than sorting, use Max. Unfortunately your date field is not a DateTime, so conversion is needed. I did the conversion first so I wouldn't have to do it twice when filtering. I also didn't assume there would be only one most recent date per operation, if you need only you could add a First. I preserved and returned the original Item at the end.
var min = from item in list
select new { item, date = DateTime.Parse(item.date) } into newitem
group newitem by newitem.item.operation into itemgroup
let maxdate = itemgroup.Max(i => i.date)
from item in itemgroup
where item.date == maxdate
select item.item;
Note: It is possible that .Net Core handles the case of OrderBy.First specially without sorting.

Related

SelectMany c# linq

Sorry to open another post.
.SelectMany with C#
I asked in the previous post but I can't get the solution for my problem.
`
var departments = stops
.Where(stop => stop.InitDate != null)
.SelectMany(stop => new[] { Month = stop.InitDate.Month, Year = stop.InitDate.Year, Duration = stop.Duration })
.GroupBy(dt => new { dt.Month, dt.Year })
.OrderBy(g => g.Key.Month)
.ThenBy(g => g.Key.Year)
.Select(g => new
{
Key = g.Key.Month,
Año = g.Key.Year,
Duration = g.Sum(v => v.Duration),
Count = g.Count()
});
`
This is de final solution to my problem, but, when I use this in my code, I have some problems.
If I don't declare the variables "Month, Year, Duration", I get an error in:
.SelectMany(stop => new[] { Month = stop.InitDate.Month, Year = stop.InitDate.Year, Duration = stop.Duration })
But I do not know what kind of data they are month and year because if I declare it how integer, I get an error in .GroupBy(dt => new { dt.Month, dt.Year }), because the compiler recognizes dt as integer.
I tried to declare Month and Year as integer and put in the .GroupBy this:
.GroupBy(dt => new { Month, Year }) but it is not correct...
Thank you in advance
Raúl
Apparently you have a sequence named Stops which is a sequence of stop objects. Each stop object may or may have not an InitDate. If it has an InitDate, this InitDate has at least properties Month, Year and Duration, which are all int.
What you want is from your original Stops, only those stop objects that have an InitDate. From every stop object you select, you want to create a new object, with a Key property which contains the Month and the Year and a Duration property which contains the Duration.
You were almost there. Your problem was that you used SelectMany instead of a simple Select.
Normally you use SelectMany if you have a sequence of sequences that you want to concatenate into one sequence. However your Stops does not have a sequence of sequences. Every stop object should produce one "new object with Year, Month and Duration".
Or in simple words: whenever you have a collection of "thingies" and you want to convert every "thingy" into exactly one "other thingy", you should use Select, not SelectMany:
In your case the Select will be:
var stopData = stops
.Where(stop => stop.InitDate != null)
.Select(stop => new
{
GroupKey = new
{
Month = stop.InitDate.Month,
Year = stop.InitDate.Year,
},
Duration = stop.Duration
});
I put the Year and the Month alreay in a property GroupKey, because that makes the grouping easier:
var groupsOfSameMonth = stopData
.GroupBy( item => item.Key, item => item.Duration);
Now every group, contains a key, which is the {Month, Year}. The elements of the group are all Durations with this {month, year}. So now all you have to do is from every group, take all elements from the group and Sum() and Count() them:
var durationsOfSameMonth = groupsOfSameMonth
.Select(group => new
{
Month = group.Key.Month, // I assumed your Key was an error
Año = group.Key.Year,
Duration = group.Sum(),
Count = group.Count()
});
All you have to do is some ordering and you are finished.

Select single item from each group in multiple groups

I have a list (specifically IEnumerable) of items of a specific class:
internal class MyItem
{
public MyItem(DateTime timestamp, string code)
{
Timestamp= timestamp;
Code = code;
}
public DateTime Timestamp { get; private set; }
public string Code { get; private set; }
}
Within this list, there will be multiple items with the same code. Each will have a timestamp, which may or may not be unique.
I'm attempting to retrieve a dictionary of MyItem's (Dictionary<string, MyItem>) where the key is the code associated with the item.
public Dictionary<string, MyItem> GetLatestCodes(IEnumerable<MyItem> items, DateTime latestAllowableTimestamp)
Given this signature, how would I retrieve the MyItem with a timestamp closest to, but not after latestAllowableTimestamp for each code?
For example, given the following for input:
IEnumerable<MyItem> items = new List<MyItem>{
new MyItem(DateTime.Parse("1/1/2014"), "1"),
new MyItem(DateTime.Parse("1/2/2014"), "2"),
new MyItem(DateTime.Parse("1/3/2014"), "1"),
new MyItem(DateTime.Parse("1/4/2014"), "1"),
new MyItem(DateTime.Parse("1/4/2014"), "2")};
If the latestAllowableTimestamp is 1/3/2014, the result would contain only the following items:
Timestamp | Code
----------------
1/3/2014 | 1
1/2/2014 | 2
I can manage to filter the list down to only those timestamps prior to latestAllowableTimestamp, but I don't know linq well enough to pick the most recent for each code and insert it into a dictionary.
var output = items.Where(t => (t.Timestamp <= latestAllowableTimestamp)).GroupBy(t => t.Code);
At this point, I've ended up with two groups, but don't know how to select a single item across each group.
Here is the actual method you are trying to write. It even returns a dictionary and everything:
static Dictionary<string, MyItem> GetLatestCodes(
IEnumerable<MyItem> items, DateTime latestAllowableTimestamp)
{
return items
.Where(item => item.TimeStamp <= latestAllowableTimestamp)
.GroupBy(item => item.Code)
.Select(group => group
.OrderByDescending(item => item.TimeStamp)
.First())
.ToDictionary(item => item.Code);
}
See Enumerable.ToDictionary
This is the your part you should have posted in your question (as LB pointed out)
var list = new List<MyItem>()
{
new MyItem(){ code = "1" , timestamp = new DateTime(2014,1,1)},
new MyItem(){ code = "2" , timestamp = new DateTime(2014,1,2)},
new MyItem(){ code = "1" , timestamp = new DateTime(2014,1,3)},
new MyItem(){ code = "1" , timestamp = new DateTime(2014,1,4)},
new MyItem(){ code = "2" , timestamp = new DateTime(2014,1,4)}
};
DateTime latestAllowableTimestamp = new DateTime(2014, 1, 3);
This is my answer
var result = list.GroupBy(x => x.code)
.Select(x => x.OrderByDescending(y => y.timestamp)
.FirstOrDefault(z => z.timestamp <= latestAllowableTimestamp))
.ToList();
To create your Dictionary, could construct your query like so:
var newDict = items.Where(a => a.Timestamp <= latestAllowableTimestamp)
.GroupBy(b => b.Timestamp)
.ToDictionary(c => c.First().Timestamp, c => c.First());
This should create a Dictionary from your data, with no duplicate days. Note that without the GroupBy query, you'll raise an exception, because ToDictionary doesn't filter out keys it's already seen.
And then if you wanted to get only one MyItem for any given code number, you could use this query:
newDict.Select(a => a.Value)
.OrderByDescending(b => b.Timestamp)
.GroupBy(c => c.Code)
.Select(d => d.First());
The FirstOrDefault query will return only one element from each group. This will give you the MyItem closest to the latest date for any given code.

How to c# List<> order by and Group by according to parameters?

I have a class and its List
abc cs = new abc();
List<abc> Lst_CS = new List<abc>();
and I set some value by HidenField in foreach loop
foreach (blah blah)
{
cs = new abc{
No = VKNT,
GuidID=hdnGuidID.Value.ToString(),
RecID=hdnRecID.Value.ToString(),
Date=HdnDate.Value.ToString()
};
Lst_CS.Add(cs);
}
and finally I get a List_CS and I order by Lst_CS according to Date like this;
IEnumerable<abc> query = Lst_CS.OrderBy(l => l.Date).ToList();
but in extra, I want to group by according to No.
Briefly, I want to order by Date and then group by No on Lst_CS How can I do ?
Thanks for your answer
Well you just just do the ordering then the grouping like so:
Lst_CS.OrderBy(l => l.Date)
.GroupBy(l => l.No)
.ToList();
Each list of items in each group will be ordered by date. The groupings will be in the order that they are found when the entire list is ordered by date.
Also your ForEach can be done in one Linq statement, then combined with the ordering and grouping:
var query = blah.Select(b => new abc{
No = VKNT,
GuidID=hdnGuidID.Value.ToString(),
RecID=hdnRecID.Value.ToString(),
Date=HdnDate.Value.ToString()
})
.OrderBy(l => l.Date)
.GroupBy(l => l.No)
.ToList();

Find MAX/MIN list item using LINQ?

I have a list Having multiple Items and 3 props ID,DATE,COMMENT.ID field is Auto incremented in DATABASE.
Let say list Contains
2,16AUG,CommentMODIFIED
1,15AUG,CommentFIRST
3,18AUG,CommentLASTModified
I want to get a single ITEM.Item Having Minimum DATE and having Latest Comment. In this case
1,15AUG,CommentLASTModified
Any easy way to do it using LINQ.
orderedItems = items.OrderBy(x => x.Date);
var result = items.First();
result.Comment = items.Last().Comment;
To get a single item out of the list, you can order the items then take the first one, like this:
var result = items
.OrderByDescending(x => x.Date)
.First();
But First will throw an exception if the items collection is empty. This is a bit safer:
var result = items
.OrderByDescending(x => x.Date)
.FirstOrDefault();
To get the min / max of different columns you can do this:
var result =
new Item {
Id = 1,
Date = items.Min(x => x.Date),
Comment = items.Max(x => x.Comment)
};
But this will require two trips to the database. This might be a bit more efficient:
var result =
(from x in items
group x by 1 into g
select new Item {
Id = 1,
Date = g.Min(g => g.Date),
Comment = g.Max(g => g.Comment)
})
.First();
Or in fluent syntax:
var result = items
.GroupBy(x => 1)
.Select(g => new Item {
Id = 1,
Date = g.Min(g => g.Date),
Comment = g.Max(g => g.Comment)
})
.First();

Use Linq to return first result for each category

I have a class (ApplicationHistory) with 3 properties:
ApplicantId, ProviderId, ApplicationDate
I return the data from the database into a list, however this contains duplicate ApplicantId/ProviderId keys.
I want to supress the list so that the list only contains the the earliest Application Date for each ApplicantId/ProviderId.
The example below is where I'm currently at, but I'm not sure how to ensure the earliest date is returned.
var supressed = history
.GroupBy(x => new
{
ApplicantId = x.ApplicantId,
ProviderId = x.ProviderId
})
.First();
All advice appreciated.
Recall that each group formed by the GroupBy call is an IGrouping<ApplicationHistory>, which implements IEnumerable<ApplicationHistory>. Read more about IGrouping here. You can order those and pick the first one:
var oldestPerGroup = history
.GroupBy(x => new
{
ApplicantId = x.ApplicantId,
ProviderId = x.ProviderId
})
.Select(g => g.OrderBy(x => x.ApplicationDate).FirstOrDefault());
You are selecting first group. Instead select first item from each group:
var supressed = history
.GroupBy(x => new {
ApplicantId = x.ApplicantId,
ProviderId = x.ProviderId
})
.Select(g => g.OrderBy(x => x.ApplicationDate).First());
Or query syntax (btw you don't need to specify names for anonymous object properties in this case):
var supressed = from h in history
group h by new {
h.ApplicantId,
h.ProviderId
} into g
select g.OrderBy(x => x.ApplicationDate).First();

Categories