I'm trying to determine a way where if a name is in the WorkedDataPoint list, and falls on any of the days in totalDaysInDateRange it counts it. The basic premice, is if these two dates match, the person worked that day - I'm trying to get a count of "worked" days. There also may be more than one entry per day per user, and we can only count the one, one for the day so DistinctBy in "morelinq"seemed a good choice...
What I have here should work fine, compiler at work so can't say for sure. But how would I do this with linq lambda expressions to make it more concise - particularly the foreach loop, can that in lambda in this application somehow?
public class WorkedDatapoint
{
public string AssignerName { get; set; }
public DateTime Date { get; set; }
public bool AssignedToOther { get; set; }
}
List<DateTime> totalDaysInDateRange
List<WorkedDataPoint> AssignersList
testtech = "Bobby"
//The two lists are loaded here
int totalDaysWorked = 0;
foreach (var day in totalDaysInDateRange)
{
if (AssignersList.Where(d => testtech.Contains(d.AssignerName)).DistinctBy(d => d.Date.Date).Any(d => d.Date.Date == day.Date)
{
totalDaysWorked++;
}
}
Edit: I think I got it but can't test til tomorrow. Any way to declare that variable TotalDaysWorked inline or is that asking too much?
int totalDaysWorked = 0;
totalDaysInDateRange.ForEach((day) =>
{
if (AssignersList
.Where(d => testtech.Contains(d.AssignerName))
.DistinctBy(d => d.Date.Date)
.Any(d => d.Date.Date == day.Date))
{ totalDaysWorked++; }});
This method is "crunching" thousands of records, so thoughts on optimization for speed (plenty of RAM and Intel-I7) would be much appreciated as well.
Can do something like this:
int totalDaysWorked = totalDaysInDateRange.Intersect(AssignersList.Select(b => b.Date)).Count();
As Except uses Set internally:
int count = totalDaysInDateRange.Count() - totalDaysInDateRange.Except(AssignersList.Select(b => b.Date)).Count();
Warning:
For large numbers, linq is incredibly slow and should be banned. More about this here:
I recommend to join totalDaysInDateRange and AssignersList on Date. Then Group by AssignerName and Date. you will get the dates
var jointList = totalDaysInDateRange.Join(
AssignersList,
p => p,
c => c.Date,
(p, c) => new { c.AssignerName, c.Date }
).Distinct().GroupBy(x => x.AssignerName).Select(group =>
new {
AssignerName = group.Key,
Count = group.Count()
}).ToList();
You can write linq query for your problem like:-
var AssignerDaysCount = assignerList.Where(filter =>
totalDaysInDateRange.Contains(filter.Date.Date))
.GroupBy(item => item.AssignerName)
.Select(lst => new { AssignerName = lst.Key, DaysCount = lst.Select(cnt => cnt.Date).Distinct().Count() })
.ToList();
Related
I construct the below query to display data from Json.
Assume this query returns 50 rows. But I want only first 10 rows to be displayed.
What is the equivalent of select top 10 or limit 10 in this scenario. Thank you.
List<Item> result = items.GroupBy(x => x.ItemNo)
.Select(x => new Item
{
ItemNo = x.Key,
ItemName = x.FirstOrDefault(y => y.Key == "ItemName")?.Value,
Date = x.FirstOrDefault(y => y.Key == "Date")?.Value
}).OrderByDescending(y => y.date)
.ToList();
In any query into your data pipeline you should NEVER request the lot. Why? You have no idea how many records there are in a query. You may think there are only a few but if a DBAdmin has done something wrong, there may be 10 million rows in the customer table.
So use a Request query object to request your data with some boundary condition values set by default.
public record ListQueryRequest {
public int StartIndex { get; init; } = 0;
public int PageSize { get; init; } = 1000;
}
Your data pipeline service can then do something like this:
private async ValueTask<ListRequestResult<TRecord>> GetItemsAsync<TRecord>(ListQueryRequest listQuery)
{
using var dbContext = this.factory.CreateDbContext();
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
IQueryable<TRecord> query = dbContext.Set<TRecord>();
/.... any IQueryable conditions such as filtering or ordering
query = query
.Skip(listQuery.StartIndex)
.Take(listQuery.PageSize);
// delayed execution of the query
var list = await query.ToListAsync();
// build and return your request result
}
Where the result object template looks like this:
public record ListQueryResult<TRecord>
{
public bool Successful {get; init;}
public string Message {get; init;}
public IEnumerable<TRecord> Items {get; init;}
}
I think that the method you are looking for is Take
Take method from Microsoft website
In the end I think your code should look like this:
List<Item> result = items.GroupBy(x => x.ItemNo)
.Select(x => new Item
{
ItemNo = x.Key,
ItemName = x.FirstOrDefault(y => y.Key == "ItemName")?.Value,
Date = x.FirstOrDefault(y => y.Key == "Date")?.Value
}).OrderByDescending(y => y.date).Take(10).ToList();
This is an entity
public class ViewModel
{
public string Id { get; set; }
public DateTime Start { get; set; }
public string Name { get; set; }
}
This is my context query,works with ef core in dbcontext.
var list = _Service.GetDataByMonth(start,end).ToList();
// it returns all entity between giving start, end param.
// start and end is Datetime property comes from ajax. This code works fine it return almost 1k objectlist with type of ViewModel
like
[0] Id="1",Title="sample",Start:""15.12.2020"
[1] Id="2",Title="sample2",Start:""15.12.2020"
[2] Id="3",Title="sample3",Start:""16.12.2020"
[3] Id="4",Title="sample4",Start:""16.12.2020"
As shows above we got almost 20+ entity per day.
I can get count per day like
var listt = _Service.GetDataByMonth(start,end).GroupBy(x => x.Start.Date).Select(grp => new { Date = grp.Key, Count = grp.Count() });
[0] Key:""15.12.2020",Count:20
[1] Key:""16.12.2020",Count:25
[2] Key:""17.12.2020",Count:44
it returns like this.
So what i want is giving start and end param a funciton then get 3 values per day between giving datetime object
NEW
var list1= _Service.GetDataByMonth(start,end).GroupBy(x => x.StartDate.Date)
.Select(grp => grp.Take(3)).ToList();
//this type List<Ienumerable<Viewmodel>>
var list2 = _Service.GetDataByMonth(start,end).GroupBy(x => x.StartDate.Date).Select(grp => grp.Take(3).ToList()).ToList();
// this type List<List<Viewmodel>>
My want it List<Viewmodel>
service
...
return entity.Where(x =>
x.IsDeleted == false &&
(x.StartDate.Date >= start.Date && x.StartDate.Date <=end.Date)
).OrderBy(x => x.FinishDate).ToList();
// it work with this way but bad way
var lis = list.SelectMany(d => d).ToList();
Yes I figuredout using selectmany instead of select works fine. Thank you again
You can use .Take() to only take 3 items of each group:
_Service.GetDataByMonth(start,end)
.GroupBy(x => x.Start.Date)
.Select(grp => new { Data = grp.Take(3).ToList(), Date = grp.Key, Count = grp.Count() })
.ToList();
var list1= _Service.GetDataByMonth(start,end).GroupBy(x => x.StartDate.Date)
.Select(grp => grp.Take(3)).ToList();
It returns List<List> maybe it will help some one bu my need is comes from with this code
This code makes list of list each element is list and contians 3 object.
var list1= _Service.GetDataByMonth(start,end).GroupBy(x => x.StartDate.Date)
.SelectMany(grp => grp.Take(3)).ToList();
Many makes just list. It mades list with ordered objects
I have the following simple statement in my Entity Framework code:
query = query
.Where(c => c.NotificationType == NotificationType.AppMessage)
.GroupBy(c => c.ConversationId)
.Select(d => d.OrderByDescending(p => p.DateCreated).FirstOrDefault());
It simply finds the latest Notification based on a group by with conversationId and select latest. Easy.
However, this is ONLY what I want if c.NotificationType == NotificationType.AppMessage. If the column is different than AppMessage (c.NotificationType <> NotificationType.AppMessage), I just want the column. What I truly Want to write is a magical statement such as:
query = query
.Where(c => (c.NotificationType <> NotificationType.AppMessage)
|| ((c.NotificationType == NotificationType.AppMessage)
.GroupBy(c => c.ConversationId)
.Select(d => d.OrderByDescending(p => p.DateCreated).FirstOrDefault()));
But this doesn't make sense because the GroupBy/Select is based on the first where statement.
How do I solve this?
The simplest way is to compose UNION ALL query using Concat at the end of your original query:
query = query
.Where(c => c.NotificationType == NotificationType.AppMessage)
.GroupBy(c => c.ConversationId)
.Select(d => d.OrderByDescending(p => p.DateCreated).FirstOrDefault())
.Concat(query.Where(c => c.NotificationType != NotificationType.AppMessage));
public class EntityClass
{
public int NotificationType { get; set; }
public int ConversationId { get; set; }
public DateTime Created { get; set; }
public static EntityClass GetLastNotification(int convId)
{
var list = new List<EntityClass>(); // Fill the values
list = list
.GroupBy(i => i.ConversationId) // Group by ConversationId.
.ToDictionary(i => i.Key, n => n.ToList()) // Create dictionary.
.Where(i => i.Key == convId) // Filter by ConversationId.
.SelectMany(i => i.Value) // Project multiple lists to ONLY one list.
.ToList(); // Create list.
// Now, you can filter it:
// 0 - NotificationType.AppMessage
// I didn't get what exactly you want to filter there, but this should give you an idea.
var lastNotification = list.OrderByDescending(i => i.Created).FirstOrDefault(i => i.NotificationType == 0);
return lastNotification;
}
}
you filter your list with "GroupBy" based on ConversationId. Next, create a dictionary from the result and make only one list (SelectMany). Then, you already have one list where should be only records with ConversationId you want.
Last part is for filtering this list - you wanted to last notification with certain NotificationType. Should be working :)
In C# I have a list locationsList which contains fields location and date,
location can appear multiple times with different dates.
If location does appear multiple times I want to only keep the one with the latest date.
Is there any way to do this using LINQ rather than iterate through the list and compare the dates of each multiple-occurring location?
I assume the field location is a string, and you can rebuild your Location object:
class Location {
public string location { get; set; }
public DateTime date { get; set; }
}
var distincts = locationsList
.GroupBy(location => location.location)
.Select(grp => new Location {
location = grp.Key,
date = grp.Max(loc => loc.date)
});
If rebuilding your object is not an option, you can use the MaxBy extension method by MoreLINQ:
var distincts = locationsList
.GroupBy(location => location.location)
.Select(grp => grp.MaxBy(loc => loc.date))
For simplification, here is a non generic version of the MaxBy method, applied to your case:
Location MaxByDate(List<Location> locations){
Location maxLocation = null;
var maxDate = DateTime.MinValue;
foreach(var location in locations) {
if (location.date > maxDate)
{
maxLocation = location;
maxDate = location.date;
}
}
return maxLocation ;
}
Edit
Another alternative proposed by Flater, slightly worst in performance due to the fact that you must loop twice in each group, one for retrieving the Max, the second loop for finding the match. But very readable and maintainable.
var distincts = locationsList
.GroupBy(location => location.location)
.Select(grp => {
var maxDate = grp.Max(location => location.date);
return grp.First(location => location.date == maxDate);
})
I am trying to perform a fairly simple order by but seem to be struggling on how to go about doing it. Take for instance I have these two classes.
public class Method
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public List<Slot> Slots { get; set; }
}
public class Slot
{
public DateTime ExpectedDeliveryDate { get; set; }
}
Using the code below I want to order by the cheapest option and then by the quickest delivery date.
var methods = new List<Method>();
methods.Add(new Method { Id = 1, Name = "Standard", Price = 0M, Slots = new List<Slot> { new Slot { ExpectedDeliveryDate = DateTime.Now.AddDays(5).Date } } });
methods.Add(new Method { Id = 2, Name = "Super Fast Next Day", Price = 0M, Slots = new List<Slot> { new Slot { ExpectedDeliveryDate = DateTime.Now.AddDays(1).Date } } });
var b = methods.OrderBy(x => x.Price)
.ThenBy(y => y.Slots.OrderBy(t => t.ExpectedDeliveryDate.Date)
.ThenBy(t => t.ExpectedDeliveryDate.TimeOfDay))
.ToList();
The trouble I am getting here is that I am getting a runtime error stating "At least one object must implement IComparable".
Although I can fix this by implementing the IComparable interface, I was wondering if it was possible to do this. I imagine there is as if I had this code (see below) it works fine.
var slots = new List<Slot>();
slots.Add(new Slot { ExpectedDeliveryDate = DateTime.Now.AddDays(5).Date });
slots.Add(new Slot { ExpectedDeliveryDate = DateTime.Now.AddDays(1).Date });
slots.Add(new Slot { ExpectedDeliveryDate = DateTime.Now.AddDays(3).Date });
slots.Add(new Slot { ExpectedDeliveryDate = DateTime.Now.Date });
var d = slots.OrderBy(x => x.ExpectedDeliveryDate);
Cheers, DS.
Apologies for the naming of variables such as xyz in example above :) Code can be copied and pasted for manipulation pleasure.
EDIT
- Updated to simplify code example.
- Expectation of result would be after successful sorting
Input
ID Name Price Slot
1 Standard 0 DateTime.Now.AddDays(5).Date
2 Super Fast 0 DateTime.Now.Date
Output
2 Super Fast 0 DateTime.Now.Date
1 Standard 0 DateTime.Now.AddDays(5).Date
So my super fast option should be top due to it being the cheapest and of course has the quickest delivery date.
You can use Enumerable.Min() to pick out the slot with the earliest date, like so:
var query = deliveryMethods
.OrderBy(x => x.Slots.Min(s => s.ExpectedDeliveryDate).Year)
.ThenBy(x => x.Slots.Min(s => s.ExpectedDeliveryDate).Month)
.ThenBy(x => x.Slots.Min(s => s.ExpectedDeliveryDate).Date)
.ToList();
Or, just
var query = deliveryMethods
.OrderBy(x => x.Slots.Min(s => s.ExpectedDeliveryDate.Date))
.ToList();
Do be aware that Min() will throw an exception when the input sequence is empty and the type being minimized is a value type. If you want to avoid the exception, you could do this:
var query2 = deliveryMethods
.OrderBy(x => x.Slots.Min(s => (DateTime?)(s.ExpectedDeliveryDate.Date)))
.ToList();
By converting the DateTime to a nullable, Min() will return a null for an empty sequence, and Method objects with empty slot list will get sorted to the beginning.
I'd like to give an explanation of why the attempt you posted in your original post wasn't working:
var xyz = deliveryMethods
.OrderBy(x => x.Slots.OrderBy(y => y.ExpectedDeliveryDate.Year))
.ThenBy(x => x.Slots.OrderBy(y => y.ExpectedDeliveryDate.Month))
.ThenBy(x => x.Slots.OrderBy(y => y.ExpectedDeliveryDate.Date))
.ToList();
It was because you were nesting OrderBys inside OrderBys.
x.Slots.OrderBy(...) produces an IEnumerable<Slot>, so you were basically telling it "compare these IEnumerable<Slot>s against each other to decide the order of the delivery methods". But Linq doesn't know how to compare an IEnumerable<Slot> against another one and decide which comes before the other (IEnumerable<Slot> does not implement IComparable<T>), so you were getting an error.
The answer, as another user has pointed out, is to give it something that can be compared. As you have afterwards clarified, that would be the earliest slot for each delivery method:
var xyz = deliveryMethods
.OrderBy(x => x.Slots.Min(y => y.ExpectedDeliveryDate))
.ToList();
This will work under the assumption that each delivery method has at least one slot, but will throw a runtime exception if any of them has zero slots (or if Slots is null). I've asked you twice what it should do in that case, and I encourage you to clarify that.
One possible solution would be to only include delivery methods that have slots:
var xyz = deliveryMethods
.Where(x => x.Slots != null && x.Slots.Any())
.OrderBy(x => x.Slots.Min(y => y.ExpectedDeliveryDate))
.ToList();