GroupBy Date in LINQ C# - c#

I am trying to GROUPBY Date in a LINQ Query and display the output as shown below
startdates: [
startdate: “4/1/2014”,
users: [
{userId, …},
{userId, …}
],
startdate: “4/2/2014”, users: [
{userId, …}
],
…
]
The code is shown below
db.Users
.Where(x => (x.startDate >= startDate) && (x.startDate <= endDate))
.GroupBy(x => new { x.startDate.Day, x.startDate.Month, x.startDate.Year })
.ToList()
.Select(y => new
{
startdates = y.Select(k =>
new {startdate = (k.startDate.Month.ToString() + "/" + k.startDate.Day.ToString() + "/" + k.startDate.Year.ToString()),
users = y.Select(z =>
new {userId = z.userId,
userName = z.userName})})});
Even though the Users are Grouped by StartDate, the output contains the startDate multiple times the same number of times as the number of Users.The output is shown below. I tried putting .Distinct() but it still repeats the startdate. Can someone please help?
[{"startdates":
[{"startdate":"04/01/2014",
"users":[
{"userId":1},"userName":"John"}
{"userId":2},"userName":"Mike"}],
[{"startdate":"04/01/2014",
"users":[
{"userId":1},"userName":"John"}
{"userId":2},"userName":"Mike"}],
[{"startdate":"04/02/2014",
"users":[
{"userId":3},"userName":"AL"}
{"userId":4},"userName":"Test"}],
[{"startdate":"04/02/2014",
"users":[
{"userId":3},"userName":"AL"}
{"userId":4},"userName":"Test"}]

The problem is your selection part, here:
.Select(y => new
{
startdates = y.Select(k =>
new {startdate = (k.startDate.Month.ToString() + "/" + k.startDate.Day.ToString() + "/" + k.startDate.Year.ToString()),
users = y.Select(z =>
new {userId = z.userId,
userName = z.userName})})});
You've got far too much nesting there. You're creating a startdate part for each element within the group.
It's unclear why you're using grouping by three separate parts at all, but I suspect this will do what you want:
db.Users
.Where(x => (x.startDate >= startDate) && (x.startDate <= endDate))
.GroupBy(x => x.startDate.Date) // Or just x.startDate
.AsEnumerable() // Do the rest of the query locally
.Select(group => new
{
startdate = group.Key.ToString("MM/dd/yyyy"),
users = group.Select(z => new { z.userId, z.userName })
});
If you need to wrap that in a startdates field, you can then use:
var result = new { startdates = query.ToArray() };

Related

Calculate sum based on nested filter

The problem
This works as expected and produces the result with the totalpax field being correct. However, the pax field for each destination, should be the sum based on the destination Id.
Code
public async Task<IEnumerable<ReservationCalendarGroupVM>> GetForCalendarAsync(string fromDate, string toDate) {
return await context.Schedules
.AsNoTracking()
.Where(x => x.Date >= Convert.ToDateTime(fromDate) && x.Date <= Convert.ToDateTime(toDate))
.GroupBy(z => new { z.Date })
.Select(e => new ReservationCalendarGroupVM {
Date = e.Key.Date.ToString(),
Destinations = e.GroupBy(i => new { i.Destination.Id, i.Destination.Abbreviation, i.Destination.Description }).Select(p => new DestinationCalendarVM {
Id = p.Key.Id,
Abbreviation = p.Key.Abbreviation,
Description = p.Key.Description,
Pax = context.Reservations.Where(y => y.Date == e.Key.Date).Sum(h => h.TotalPersons)
}),
TotalPax = context.Reservations.Where(y => y.Date == e.Key.Date).Sum(h => h.TotalPersons).ToListAsync();
}
Result
"date": "2022-07-02",
"destinations": [
{
"id": 1,
"description": "PAXOS - ANTIPAXOS",
"abbreviation": "PA",
"pax": 254
},
{
"id": 3,
"description": "BLUE LAGOON",
"abbreviation": "BL",
"pax": 254
}
],
"totalpax": 432
I believe the problem lies in this line:
Pax = context.Reservations.Where(y => y.Date == e.Key.Date).Sum(h => h.TotalPersons)
You're filtering exclusively by Date, but you need to filter simultaneously by Destination. As you didn't share the models it's not very easy to infer, but I believe you'll need to do the following:
Pax = context.Reservations
.Where(y => y.Date == e.Key.Date && y.Destination.Id == e.Key.Id).Sum(h => h.TotalPersons)

Transform sql query to linq with groupBy and months

I have following query:
select concat(Left(DateName(month,[date]),3), ' ', Year([date])),
sum(TotalAttendants) as Total,
Sum(FemaleAttendants) as Women,
Sum(MaleAttendants) as Men
from dbo.Events
where IsDeleted=0 and EventTypeId = 1
group by concat(Left(DateName(month,[date]),3), ' ', Year([date]))
and I want to transform it to c# linq lambda expression.
I tried something like this:
var response = await _context.Events
.Where(x => !x.IsDeleted && x.EventTypeId == Domain.Enums.EventTypes.DirectBeneficiaries)
.GroupBy(x => x.Date)
.Select(x => new EventViewData
{
MaleAttendants = x.Sum(u => u.MaleAttendants),
FemaleAttendants = x.Sum(u => u.FemaleAttendants),
TotalAttendants = x.Sum(u => u.TotalAttendants),
MonthName = x.Key.ToString("00")
}).ToListAsync();
Im not getting same result as Im getting in my mssql management studio.
If you need more information about data structure and table Events here is the my another stackoverflow topic: link
I think you should group by month and year and do the formatting (concat, etc.) later (if needed at all).
select
...
from dbo.Events
..
group by Month([date]), Year([date]))
Then in linq you can:
...
.GroupBy(x => new { Year = x.Date.Year, Month = x.Date.Month } )
.Select(x => new // Note no type name
{
MaleAttendants = x.Sum(u => u.MaleAttendants),
FemaleAttendants = x.Sum(u => u.FemaleAttendants),
TotalAttendants = x.Sum(u => u.TotalAttendants),
Month = x.Key.Month,
Year = x.Key.Year
})
.ToListAsync() // Hit the db
.Select( x => new EventViewData
{
x.MaleAttendants
x.FemaleAttendants
x.TotalAttendants
MonthName = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedMonthName(x.Month)
...
}
I don't think GetAbbreviatedMonthName is supported by EF so we need to do it after ToListAsync.

Performing multiple Linq queries against the same Linq result

I have created a dashboard that all data displayed on it shares 4 common elements (startDate,endDate,CompanyID,StoreID) that are used as Where clauses in a Linq statement. The result of that statement is then queried in a variety of ways to group and sort the data and used in charts, lists etc. Here is a short snippit to show the duplication that is currently going on:
var dashboardEntity = new BlueStreakSalesDWEntities();
//Get Total Sales
ViewBag.companySalesTotal = dashboardEntity.FactSales.Where(d => d.DateKey >= startDate)
.Where(d => d.DateKey <= endDate)
.Where(c => c.CompanyID == companyID)
.Sum(a => a.Amount);
//get list of all items sold
var companyStoreTotalItem = dashboardEntity.FactSales.Where(d => d.DateKey >= startDate)
.Where(d => d.DateKey <= endDate)
.Where(c => c.CompanyID == companyID).GroupBy(m => new { m.Description })
.Select(g => new DescriptionAmountModel { Amount = g.Sum(a => a.Amount).Value, Description = g.Key.Description })
.OrderByDescending(x => x.Amount);
I have like 15 of these calls on the dashboard and it can get very slow at times from what I imagine are multiple calls when in reality the database only needs to be queried once then that result needs to be queried for different results.
How can I do this?
Any help would be greatly appreciated
In your current solution each query executes separatly, on the same data. You can first execute the shared parts of the queries and bring the results from database. In your examples it is these where conditions
//Executes in database
var entities = dashboardEntity.FactSales.Where(d => d.DateKey >= startDate)
.Where(d => d.DateKey <= endDate)
.Where(c => c.CompanyID == companyID)
.ToList();
Now that this data is filtered to only what you want you can in memory do the rest of the aggregations:
//Happens in the List<T> in memory
ViewBag.companySalesTotal = entities.Sum(a => a.Amount);
var companyStoreTotalItem = entities.GroupBy(m => new { m.Description })
.Select(g => new DescriptionAmountModel { Amount = g.Sum(a => a.Amount).Value, Description = g.Key.Description })
.OrderByDescending(x => x.Amount);
This way you can make efficient. This make the query execute single time in database and rest of the part happen on the pullout in memory data
var result = dashboardEntity.FactSales.Where(d => d.DateKey >= startDate && d => d.DateKey <= endDate && d.CompanyID == companyID).ToList();
ViewBag.companySalesTotal = result.Sum(a => a.Amount);
//then get list of all items sold from in memory data
var companyStoreTotalItem = result.GroupBy(m => new { m.Description }).Select(g => new DescriptionAmountModel { Amount = g.Sum(a => a.Amount).Value, Description = g.Key.Description }).OrderByDescending(x => x.Amount);

Group By using more than two columns by Lambda expression

I have to convert my given linq query in lambda expression. i.e.
var scholars = (from scholar in db.Scholars
join suspension in db.Suspensions
on scholar.ID equals suspension.ScholarID
where suspension.StartDate >= startDate &&
suspension.EndDate <= endDate
group scholar by new { scholar.ID, scholar.FirstName, scholar.LastName }
into g
select new
{
FullName = g.Key.FirstName +" " + g.Key.LastName,
TotalSuspensionSum = g.Sum(x => x.Suspensions.Sum(y => y.SuspensionDays))
})
.ToList()
.OrderBy(x=> x.FullName);
this is your lambda:
var scholars = db.Scholars.Join(db.Suspensions,
scholar => scholar.ID,
suspension => suspension.ScholarID,
(scholar, suspension) => new {scholar, suspension})
.Where(u => u.suspension.StartDate >= startDate &&
u.suspension.EndDate <= endDate)
.GroupBy(u => new { u.scholar.ID, u.scholar.FirstName, u.scholar.LastName })
.Select(u => new
{
FullName = u.Key.FirstName + " " + u.Key.LastName,
TotalSuspensionSum = u.Sum(x =>
x.scholar.Suspensions.Sum(y => y.SuspensionDays)
)
})
.OrderBy(x => x.FullName)
.ToList();
Well I don't think I should do all your work for you but specifically the group by you are asking about could be done like:
...GroupBy(x => new { x.ID, x.FirstName, x.LastName })...

LINQ query to group records by month within a period

I am looking for some help on adapting the following LINQ query to return all dates within the next 6 months, even those where no records fall within the given month.
var maxDate = DateTime.Now.AddMonths(6);
var orders = (from ord in db.Items
where (ord.Expiry >= DateTime.Now && ord.Expiry <= maxDate)
group ord by new
{
ord.Expiry.Value.Year,
ord.Expiry.Value.Month
}
into g
select new ExpiriesOwnedModel
{
Month = g.Select(n => n.Expiry.Value.Month).First(),
Quantity = g.Count()
}).ToList();
I'd really appreciate any assistance or pointers on how best to implement this.
I'm not sure how well it'll interact with your database, but I'd do this as with a join:
var firstDaysOfMonths = Enumerable.Range(0, 7).Select(i =>
new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1).AddMonths(i));
var orders = firstDaysOfMonths.GroupJoin(
db.Items,
fd => fd,
ord => new DateTime(ord.Expiry.Value.Year, ord.Expiry.Value.Month, 1),
(fd, ords) => new { Month = fd.Month, Quantity = ords.Count() });
Note you may end up with an extra month where before you didn't (on the first day of the month?)
Stolen from Rawling's answer, if you prefer query syntax for group joins (I do):
var orders =
from month in Enumerable.Range(0, 7)
.Select(i => new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1).AddMonths(i))
join ord in db.Items
on month equals new DateTime(ord.Expiry.Value.Year, ord.Expiry.Value.Month, 1)
into ords
select new { month.Month, Quantity = ords.Count() };
Alternative if it does not play nice with the database:
var rawGroups = db.Items.Where(item.Expiry >= DateTime.Now && ord.Expiry <= maxDate)
.GroupBy(item => new
{
item.Expiry.Value.Year,
item.Expiry.Value.Month
}, g => new ExpiriesOwnedModel()
{
Month = g.Key.Month,
Quantity = g.Count()
}).ToDictionary(model => model.Month);
var result = Enumerable.Range(DateTime.Now.Month,6)
.Select(i => i > 12 ? i - 12 , i)
.Select(i => rawGroups.Keys.Contains(i) ?
rawGroups[i] :
new ExpiriesOwnedModel()
{ Month = i , Quantity = 0 });

Categories

Resources