简体   繁体   中英

Pivot table from sql to linq

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: SQL 表

*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].

  • Well, not from all Events, only those that are not Deleted
  • And from those that were not deleted, you only want the ones that match the ProjectId and the Year

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.

This won't fill your table

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM