linq group by: good syntax but weird output

The syntax of my query is good but not the output and it's really strange.

I have the following table:

| AppointID | UserID | AppointSet | AppointAttended | AppointCancelled | AppointRescheduled | AppointmentDatetime
|     1     |   1    |  2/15/2011 |                 |   3/11/2011      |                    |  3/15/2011
|     2     |   1    |  2/17/2011 |                 |                  |    3/11/2011       |  3/10/2011
|     3     |   1    |  3/11/2011 |   3/11/2011     |                  |                    |  3/25/2011
|     4     |   1    |  3/10/2011 |                 |   3/11/2011      |                    |  3/11/2011

What I'm trying to do is create the following output that counts the activity by day.

|    Date     |   Set   |   Attended   |   Rescheduled   |   Cancelled   |
|  3/10/2011  |    1    |              |                 |               |
|  3/11/2011  |    1    |      1       |       1         |      2        |

Note that I've defined the fields AppointAttended, AppointCancelled and AppointRescheduled as nullable because there might not be a date for these.

The query is as follows:

var OutputMonthlyActivity = from appnt in MyDC.LeadsAppointments
where appnt.UserID == TheUserID
where (appnt.AppointSet.Year == TheDate.Year && appnt.AppointSet.Month == TheDate.Month) ||
(appnt.AppointAttended.Value.Year == TheDate.Year && appnt.AppointAttended.Value.Month == TheDate.Month) ||
(appnt.AppointRescheduled.Value.Year == TheDate.Year && appnt.AppointRescheduled.Value.Month == TheDate.Month) ||
(appnt.AppointCancelled.Value.Year == TheDate.Year && appnt.AppointCancelled.Value.Month == TheDate.Month)
group appnt by new { SetOn = appnt.AppointSet.Date, ReschedOn = appnt.AppointRescheduled.Value.Date, Attended = appnt.AppointAttended.Value.Date, Cancelled = appnt.AppointCancelled.Value.Date } into daygroups
select new ViewMonthlyActivityModel()
{

ViewDate = (from d in daygroups select d.AppointDatetime.Date).First(),

CountTotalSetOnDay = (from c in daygroups
where c.AppointSet.Date == daygroups.Key.SetOn
select c.AppointID).Count(),

CountAttendedOnDay = (from c in daygroups
where c.AppointAttended.HasValue == true
select c.AppointID).Count(),

CountRescheduledOnDay = (from c in daygroups
where c.AppointRescheduled.HasValue == true
select c.AppointID).Count(),

CountCancelledOnDay = (from c in daygroups
where c.AppointCancelled.HasValue == true
select c.AppointID).Count()

};

TheDate is a parameter I pass that represents a date in the month I'm querying (ie March 1st). The problem is that in addition to all the dates of March, I'm getting results from the last 2 days of February and the first 2 days of April. Also, each day comes out with 3 rows.

If you have any suggestions, that'd be really appreciated.

Thanks.


The problem seems to be that your group has some anonymous object as its key.

You are grouping each appointment by an object:

new { SetOn = appnt.AppointSet.Date, ReschedOn = appnt.AppointRescheduled.Value.Date, Attended = appnt.AppointAttended.Value.Date, Cancelled = appnt.AppointCancelled.Value.Date } into daygroups

Instead, you should be grouping by the actual key, such as the AppointmentDatetime.

Actually, for what you are trying to do, I don't believe you need to group by at all.

Assuming the following sample data:

// First, get all appointments for the following user
var TheUserID = 1;
var appointments = from appointment in Appointments where appointment.UserID == TheUserID select appointment;

// Then, find all unique dates
var uniqueDates = appointments.Select(a => a.AppointSet)
    .Union(appointments.Where(a => a.AppointAttended.HasValue).Select(a => a.AppointAttended.Value))
    .Union(appointments.Where(a => a.AppointCancelled.HasValue).Select(a => a.AppointCancelled.Value))
    .Union(appointments.Where(a => a.AppointRescheduled.HasValue).Select(a => a.AppointRescheduled.Value))
    .Union(appointments.Select(a => a.AppointmentDatetime))
      // Filter by the month/year
    .Where(x => x.Month == TheDate.Month && x.Year == TheDate.Year)
      // Finally, select a new object for each unique date holding the counts
    .Select(x => new {
        Date = x,
        Cancelled = appointments.Where(a => a.AppointCancelled.HasValue && a.AppointCancelled.Value == x).Count(),
        Rescheduled = appointments.Where(a => a.AppointRescheduled.HasValue && a.AppointRescheduled.Value == x).Count(),
        Set = appointments.Where(a => a.AppointSet == x).Count(),
        Attended = appointments.Where(a => a.AppointAttended.HasValue && a.AppointAttended.Value == x).Count()
    });

This should result in the following output:

Is this what you're trying to do?


您可以为每列生成单独的计数,然后将它们连接在一起,就像这样(我只对AppointSet和AppointAttend做了它,但其余的将很简单):

        var userAppoints = from appnt in MyDC.LeadsAppointments
                           where appnt.UserID == TheUserID
                           select appnt;

        var appntSets = from appnt in userAppoints
                        where appnt.AppointSet.Year == TheDate.Year && appnt.AppointSet.Month == TheDate.Month
                        group appnt by appnt.AppointSet
                        into groups
                        select new ViewMonthlyActivityModel()
                            {
                                ViewDate = groups.Key,
                                CountTotalSetOnDay = groups.Count()
                            };

        var appntAttends = from appnt in userAppoints
                           where appnt.AppointAttended != null && appnt.AppointAttended.Value.Year == TheDate.Year && appnt.AppointAttended.Value.Month == TheDate.Month
                           group appnt by appnt.AppointAttended.Value
                           into groups
                           select new ViewMonthlyActivityModel()
                               {
                                   ViewDate = groups.Key,
                                   CountAttendedOnDay = groups.Count()
                               };

        var allModels = appntSets.Concat(appntAttends).GroupBy(a => a.ViewDate, (date, models) => new ViewMonthlyActivityModel 
        { 
            ViewDate = date, 
            CountTotalSetOnDay = models.Aggregate(0, (seed, model) => seed + model.CountTotalSetOnDay), 
            CountAttendedOnDay = models.Aggregate(0, (seed, model) => seed + model.CountAttendedOnDay) 
        });

If you can't change the schema, I'd start by reshaping the data a little:

var appoinments = MyDC
.LeadsAppointments
.Select(la => new 
    {
        somefields = somedata, //fill in the blanks
        resolveDate =
            la.AppointAttended  ??
            la.AppointCancelled ??
            la.AppointRescheduled,
        resolveReason = 
            la.AppointAttended != null ? 0 : la.AppointCancelled != null ? 1 : 2
    })

Now we make a 2 flat tables of dates vs reasons (0,1,2 are attended,cancelled,rescheduled and 4 is set)

var setDates = appointments
    .Select(a => new {Date = a.Date, Reason = 4});

var otherDates = appointments
    .Select(a => new {Date = a.resolveDate,Reason = a.ResolveReason});

UnionAll the two tables above:

var allDates = setDates.Concat(otherDates);

And group:

var groups = allDates.GroupBy(a => new{a.Date, a.Reason});

So to query a specific day:

groups
    .Where(g => g.Key.Date == someDate)
    .Select(g=> new{g.Key.Reason, Count = g.Key.Count()})

which should (if my completely untested code is anywhere near working) show 4 rows with counts for each reason.

Not the shape you want, but it wouldn't be difficult to pivot if that's what you need.

链接地址: http://www.djcxy.com/p/31910.html

上一篇: LINQ聚合算法解释

下一篇: linq group通过:良好的语法,但奇怪的输出