I have following two tables Events
and EventType
I need to do something in order to present my columns EventType names (Direct beneficiaries, Capacity Development, Reach, Online Reach) and Total column which is the sum of all these columns (in particular row):
My sql tables are structered as it is on this image:
*Note: If there no eventTypeId in table Events, then, don't show in pivot table that column.
Those numbers presents TotalAttendants for all events that held up in january, february, etc.
If there no cumulative events in March, that month should be empty (March will be here also, but instead of numbers, just empty spaces).
I tried following linq expression:
var response = _context.Events
.Where(x => query.ProjectId.HasValue ? x.ProjectId.Equals(query.ProjectId) : true)
.Where(x => x.Date.Year.Equals(query.Year))
.Where(x => !x.IsDeleted)
.GroupBy(x => new { Year = x.Date.Year, Month = x.Date.Month })
.Select(x => new
{
DirectBeneficiaries = x.Where(m => m.EventTypeId == Domain.Enums.EventType.DirectBeneficiaries)
.Select(u => new EventTotal
{
Month = u.Date.Month.ToString("MMMM"),
FemaleAttendants = x.Sum(i => i.FemaleAttendants),
MaleAttendants = x.Sum(i => i.MaleAttendants),
TotalAttendants = x.Sum(i => i.TotalAttendants)
}),
CapacityDevelopment = x.Where(m => m.EventTypeId == Domain.Enums.EventType.CapacityDevelopment)
.Select(u => new EventTotal
{
Month = u.Date.Month.ToString("MMMM"),
FemaleAttendants = x.Sum(i => i.FemaleAttendants),
MaleAttendants = x.Sum(i => i.MaleAttendants),
TotalAttendants = x.Sum(i => i.TotalAttendants)
}),
Reach = x.Where(m => m.EventTypeId == Domain.Enums.EventType.Reach)
.Select(u => new EventTotal
{
Month = u.Date.Month.ToString("MMMM"),
FemaleAttendants = x.Sum(i => i.FemaleAttendants),
MaleAttendants = x.Sum(i => i.MaleAttendants),
TotalAttendants = x.Sum(i => i.TotalAttendants)
}),
OnlineReach = x.Where(m => m.EventTypeId == Domain.Enums.EventType.OnlineReach)
.Select(u => new EventTotal
{
Month = u.Date.Month.ToString("MMMM"),
FemaleAttendants = x.Sum(i => i.FemaleAttendants),
MaleAttendants = x.Sum(i => i.MaleAttendants),
TotalAttendants = x.Sum(i => i.TotalAttendants)
})
});
but Im getting anonymous type at the end, and I dont know how to cast to my EventTotal
model:
public class EventTotal
{
public string Month { get; set; }
public int? FemaleAttendants { get; set; }
public int? MaleAttendants { get; set; }
public int? TotalAttendants { get; set; }
}
My questions is: how to convert it to a specific model?
Update #1:
I tried with this code but I have an db exception.
"Column 'Events.TotalAttendants' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.\r\nThe multi-part identifier \"e.Date\" could not be bound.\r\nThe multi-part identifier \"e.Date\" could not be bound.\r\nThe multi-part identifier \"e.Date\" could not be bound.\r\nThe multi-part identifier \"e.EventTypeId\" could not be bound.\r\nThe multi-part identifier \"e.EventTypeId\" could not be bound.\r\nThe multi-part identifier \"e.EventTypeId\" could not be bound.\r\nThe multi-part identifier \"e.EventTypeId\" could not be bound.\r\nThe multi-part identifier \"e.EventTypeId\" could not be bound.\r\nThe multi-part identifier \"e.EventTypeId\" could not be bound.\r\nThe multi-part identifier \"e.EventTypeId\" could not be bound.\r\nThe multi-part identifier \"e.EventTypeId\" could not be bound."
this is the code
var eligibleEvents = _context.Events
.Where(x => query.ProjectId.HasValue ? x.ProjectId.Equals(query.ProjectId) : true)
.Where(x => x.Date.Year.Equals(query.Year))
.Where(x => !x.IsDeleted)
.GroupBy(x => new { Year = x.Date.Year, Month = x.Date.Month, EventType = (int)x.EventTypeId });
var eventTotals = eligibleEvents.GroupBy(x => new { Year = x.Key.Year, Month = x.Key.Month },
(yearMonthCombination, eventsWithThisYearMonthCombination) => new EventTotal
{
Month = yearMonthCombination.Month,
CapacityDevelopment = eventsWithThisYearMonthCombination.Where(u => u.Key.EventType == (int)Domain.Enums.EventType.CapacityDevelopment).Select(u => u.Sum(s => s.TotalAttendants)).Sum(),
DirectBeneficiaries = eventsWithThisYearMonthCombination.Where(u => u.Key.EventType == (int)Domain.Enums.EventType.DirectBeneficiaries).Select(u => u.Sum(s => s.TotalAttendants)).Sum(),
OnlineReach = eventsWithThisYearMonthCombination.Where(u => u.Key.EventType == (int)Domain.Enums.EventType.OnlineReach).Select(u => u.Sum(s => s.TotalAttendants)).Sum(),
Reach = eventsWithThisYearMonthCombination.Where(u => u.Key.EventType == (int)Domain.Enums.EventType.Reach).Select(u => u.Sum(s => s.TotalAttendants)).Sum(),
Total = eventsWithThisYearMonthCombination.Select(u => u.Sum(s => s.TotalAttendants)).Sum(),
TotalAttendants = eventsWithThisYearMonthCombination.Select(u => u.Sum(s => s.TotalAttendants)).Sum()
}).ToList();
return eventTotals;
So from your sequence of Events
, you want to extract a sequence of EventTotals
: one EventTotal per group of Events that are from the same [Year, Month].
First of all: we can optimize the start of your query: put everything in one Where
:
var eligibleEvents = dbContext.Events
.Where(event => !event.IsDeleted
&& event.Date.Year == query.Year)
&& (!query.ProjectId.HasValue || query.ProjectId == event.ProjectId)
In words: from all Events, keep only those events that are not deleted yet, and that have a Date.Year
that equals query.Year
. Depending on whether query.ProjectId
is null or not, you keep all these events, or you keep only those events with a value for ProjectId
that equals query.ProjectId
Consider to simplify the OR as follows:
(query.ProjectId == null) || (query.ProjectId == event.ProjectId)
Now from all remaining Events
, we make groups of Events. All Events that have the same [Year, Month] combination will be in one group. From every Group we make exactly one EventTotal
.
If you use GroupBy, and you want to create one object from every Group, like we do, consider to use the overload of GroupBy that has a parameter resultSelector
Continuing the LINQ:
var eventTotals = eligibleEvents.GroupBy(event => new
{
Year = event.Date.Year,
Month = event.Date.Month,
},
// parameter resultSelector: from every [Year, Month] combination,
// and all eligible events that have these values of Year / Month,
// make one new EventTotal:
(yearMonthCombination, eventsWithThisYearMonthCombination) => new EventTotal
{
Month = yearMonthCombination.Month,
// Count the zero or more FemaleAttendants:
FemaleAttendants = eventsWithThisYearMonthCombination
.Select(event => event.FemaleAttendants)
.Sum(),
// Count the zero or more MaleAttendants:
MaleAttendants = eventsWithThisYearMonthCombination
.Select(event => event.MaleAttendants)
.Sum(),
TotalAttendants = eventsWithThisYearMonthCombination
.Select(event => event.TotalAttendants)
.Sum(),
}
If TotalAttendants
is always the sum of the FemaleAttendants and MaleAttendants, why is it a separate column? Consider to change the property:
public int TotalAttendants => this.FemaleAttendants + this.MaleAttendants;
By the way, why are your attendants nullable? is there a difference between zero attendants and null attendants? If not, consider to remove the nullable, you only make life more difficult for you.
If you really want if nullable, when should zero attendants be converted to null? Alas you forgot to define this.
You specifically asked for EventTotals
. I don't see how this would help you to fill the table.
If you want a row with [Month, DirectBenificaries, CapacityDevelopment, ...], I would go for a different resultSelector:
(yearMonthCombination, eventsWithThisYearMonthCombination) => new
{
Month = yearMonthCombination.Month,
DirectBenificiaries = eventsWithThisYearMonthCombination
.Where(event => event.EventTypeId == dbContext.EventTypes
.Where(eventType => eventType.Name == "DirectBenificiaries")
.Select(event => ...)
.Sum(),
Alas, you didn't tell us what property you use to make up the DirectBenificiaries of Jan 2020. Is it the total number of events with type DirectBenificiaries? Or is if the total number of Attendants in January 2020 with DirectBenificiaries?
The other columns are similar.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.