/// <summary> /// Binds the attendees grid. /// </summary> private void BindAttendeesGrid() { var dateRange = SlidingDateRangePicker.CalculateDateRangeFromDelimitedValues( drpSlidingDateRange.DelimitedValues ); if ( dateRange.End == null || dateRange.End > RockDateTime.Now ) { dateRange.End = RockDateTime.Now; } var rockContext = new RockContext(); // make a qryPersonAlias so that the generated SQL will be a "WHERE .. IN ()" instead of an OUTER JOIN (which is incredibly slow for this) var qryPersonAlias = new PersonAliasService( rockContext ).Queryable(); var qryAttendance = new AttendanceService( rockContext ).Queryable(); qryAttendance = qryAttendance.Where( a => a.DidAttend.HasValue && a.DidAttend.Value ); var groupType = this.GetSelectedTemplateGroupType(); var qryAllVisits = qryAttendance; if ( groupType != null ) { var childGroupTypeIds = new GroupTypeService( rockContext ).GetChildGroupTypes( groupType.Id ).Select( a => a.Id ); qryAllVisits = qryAttendance.Where( a => childGroupTypeIds.Any( b => b == a.Group.GroupTypeId ) ); } else { return; } var groupIdList = new List<int>(); string groupIds = GetSelectedGroupIds().AsDelimited( "," ); if ( !string.IsNullOrWhiteSpace( groupIds ) ) { groupIdList = groupIds.Split( ',' ).AsIntegerList(); qryAttendance = qryAttendance.Where( a => a.GroupId.HasValue && groupIdList.Contains( a.GroupId.Value ) ); } //// If campuses were included, filter attendances by those that have selected campuses //// if 'null' is one of the campuses, treat that as a 'CampusId is Null' var includeNullCampus = clbCampuses.SelectedValues.Any( a => a.Equals( "null", StringComparison.OrdinalIgnoreCase ) ); var campusIdList = clbCampuses.SelectedValues.AsIntegerList(); // remove 0 from the list, just in case it is there campusIdList.Remove( 0 ); if ( campusIdList.Any() ) { if ( includeNullCampus ) { // show records that have a campusId in the campusIdsList + records that have a null campusId qryAttendance = qryAttendance.Where( a => ( a.CampusId.HasValue && campusIdList.Contains( a.CampusId.Value ) ) || !a.CampusId.HasValue ); } else { // only show records that have a campusId in the campusIdList qryAttendance = qryAttendance.Where( a => a.CampusId.HasValue && campusIdList.Contains( a.CampusId.Value ) ); } } else if ( includeNullCampus ) { // 'null' was the only campusId in the campusIds parameter, so only show records that have a null CampusId qryAttendance = qryAttendance.Where( a => !a.CampusId.HasValue ); } // have the "Missed" query be the same as the qry before the Main date range is applied since it'll have a different date range var qryMissed = qryAttendance; if ( dateRange.Start.HasValue ) { qryAttendance = qryAttendance.Where( a => a.StartDateTime >= dateRange.Start.Value ); } if ( dateRange.End.HasValue ) { qryAttendance = qryAttendance.Where( a => a.StartDateTime < dateRange.End.Value ); } // we want to get the first 2 visits at a minimum so we can show the date in the grid int nthVisitsTake = 2; int? byNthVisit = null; if ( radByVisit.Checked ) { // If we are filtering by nth visit, we might want to get up to first 5 byNthVisit = ddlNthVisit.SelectedValue.AsIntegerOrNull(); if ( byNthVisit.HasValue && byNthVisit > 2 ) { nthVisitsTake = byNthVisit.Value; } } ChartGroupBy groupBy = hfGroupBy.Value.ConvertToEnumOrNull<ChartGroupBy>() ?? ChartGroupBy.Week; IQueryable<PersonWithSummary> qryByPersonWithSummary = null; if ( byNthVisit.HasValue && byNthVisit.Value == 0 ) { // Show members of the selected groups that did not attend at all during selected date range // Get all the person ids that did attend var attendeePersonIds = qryAttendance.Select( a => a.PersonAlias.PersonId ); // Get all the active members of the selected groups who have no attendance within selected date range and campus qryByPersonWithSummary = new GroupMemberService( rockContext ) .Queryable().AsNoTracking() .Where( m => groupIdList.Contains( m.GroupId ) && !attendeePersonIds.Contains( m.PersonId ) && m.GroupMemberStatus == GroupMemberStatus.Active ) .Select( m => new PersonWithSummary { PersonId = m.PersonId, FirstVisits = new DateTime[] { }.AsQueryable(), LastVisit = new AttendancePersonAlias(), AttendanceSummary = new DateTime[] { }.AsQueryable() } ); } else { var qryAttendanceWithSummaryDateTime = qryAttendance.GetAttendanceWithSummaryDateTime( groupBy ); var qryGroup = new GroupService( rockContext ).Queryable(); var qryJoinPerson = qryAttendance.Join( qryPersonAlias, k1 => k1.PersonAliasId, k2 => k2.Id, ( a, pa ) => new { CampusId = a.CampusId, GroupId = a.GroupId, ScheduleId = a.ScheduleId, StartDateTime = a.StartDateTime, PersonAliasId = pa.Id, PersonAliasPersonId = pa.PersonId } ); var qryJoinFinal = qryJoinPerson.Join( qryGroup, k1 => k1.GroupId, k2 => k2.Id, ( a, g ) => new AttendancePersonAlias { CampusId = a.CampusId, GroupId = a.GroupId, GroupName = g.Name, ScheduleId = a.ScheduleId, StartDateTime = a.StartDateTime, PersonAliasId = a.PersonAliasId, PersonAliasPersonId = a.PersonAliasPersonId } ); var qryByPerson = qryJoinFinal.GroupBy( a => a.PersonAliasPersonId ).Select( a => new { PersonId = a.Key, Attendances = a } ); int? attendedMinCount = null; int? attendedMissedCount = null; DateRange attendedMissedDateRange = new DateRange(); if ( radByPattern.Checked ) { attendedMinCount = tbPatternXTimes.Text.AsIntegerOrNull(); if ( cbPatternAndMissed.Checked ) { attendedMissedCount = tbPatternMissedXTimes.Text.AsIntegerOrNull(); attendedMissedDateRange = new DateRange( drpPatternDateRange.LowerValue, drpPatternDateRange.UpperValue ); if ( !attendedMissedDateRange.Start.HasValue || !attendedMissedDateRange.End.HasValue ) { nbMissedDateRangeRequired.Visible = true; return; } } } nbMissedDateRangeRequired.Visible = false; // get either the first 2 visits or the first 5 visits (using a const take of 2 or 5 vs a variable to help the SQL optimizer) qryByPersonWithSummary = qryByPerson.Select( a => new PersonWithSummary { PersonId = a.PersonId, FirstVisits = qryAllVisits.Where( b => qryPersonAlias.Where( pa => pa.PersonId == a.PersonId ).Any( pa => pa.Id == b.PersonAliasId ) ).Select( s => s.StartDateTime ).OrderBy( x => x ).Take( 2 ), LastVisit = a.Attendances.OrderByDescending( x => x.StartDateTime ).FirstOrDefault(), AttendanceSummary = qryAttendanceWithSummaryDateTime.Where( x => qryPersonAlias.Where( pa => pa.PersonId == a.PersonId ).Any( pa => pa.Id == x.Attendance.PersonAliasId ) ).GroupBy( g => g.SummaryDateTime ).Select( s => s.Key ) } ); if ( nthVisitsTake > 2 ) { qryByPersonWithSummary = qryByPerson.Select( a => new PersonWithSummary { PersonId = a.PersonId, FirstVisits = qryAllVisits.Where( b => qryPersonAlias.Where( pa => pa.PersonId == a.PersonId ).Any( pa => pa.Id == b.PersonAliasId ) ).Select( s => s.StartDateTime ).OrderBy( x => x ).Take( 5 ), LastVisit = a.Attendances.OrderByDescending( x => x.StartDateTime ).FirstOrDefault(), AttendanceSummary = qryAttendanceWithSummaryDateTime.Where( x => qryPersonAlias.Where( pa => pa.PersonId == a.PersonId ).Any( pa => pa.Id == x.Attendance.PersonAliasId ) ).GroupBy( g => g.SummaryDateTime ).Select( s => s.Key ) } ); } if ( byNthVisit.HasValue ) { // only return attendees where their nth visit is within the selected daterange int skipCount = byNthVisit.Value - 1; qryByPersonWithSummary = qryByPersonWithSummary.Where( a => a.FirstVisits.OrderBy( x => x ).Skip( skipCount ).Take( 1 ).Any( d => d >= dateRange.Start && d < dateRange.End ) ); } if ( attendedMinCount.HasValue ) { qryByPersonWithSummary = qryByPersonWithSummary.Where( a => a.AttendanceSummary.Count() >= attendedMinCount ); } if ( attendedMissedCount.HasValue ) { if ( attendedMissedDateRange.Start.HasValue && attendedMissedDateRange.End.HasValue ) { var attendedMissedPossible = GetPossibleAttendancesForDateRange( attendedMissedDateRange, groupBy ); int attendedMissedPossibleCount = attendedMissedPossible.Count(); qryMissed = qryMissed.Where( a => a.StartDateTime >= attendedMissedDateRange.Start.Value && a.StartDateTime < attendedMissedDateRange.End.Value ); var qryMissedAttendanceByPersonAndSummary = qryMissed.GetAttendanceWithSummaryDateTime( groupBy ) .GroupBy( g1 => new { g1.SummaryDateTime, g1.Attendance.PersonAlias.PersonId } ) .GroupBy( a => a.Key.PersonId ) .Select( a => new { PersonId = a.Key, AttendanceCount = a.Count() } ); var qryMissedByPerson = qryMissedAttendanceByPersonAndSummary .Where( x => ( attendedMissedPossibleCount - x.AttendanceCount ) >= attendedMissedCount ); // filter to only people that missed at least X weeks/months/years between specified missed date range qryByPersonWithSummary = qryByPersonWithSummary.Where( a => qryMissedByPerson.Any( b => b.PersonId == a.PersonId ) ); } } } var personService = new PersonService( rockContext ); // Filter by dataview var dataViewId = dvpDataView.SelectedValueAsInt(); if ( dataViewId.HasValue ) { var dataView = new DataViewService( _rockContext ).Get( dataViewId.Value ); if ( dataView != null ) { var errorMessages = new List<string>(); ParameterExpression paramExpression = personService.ParameterExpression; Expression whereExpression = dataView.GetExpression( personService, paramExpression, out errorMessages ); SortProperty sort = null; var dataViewPersonIdQry = personService .Queryable().AsNoTracking() .Where( paramExpression, whereExpression, sort ) .Select( p => p.Id ); qryByPersonWithSummary = qryByPersonWithSummary.Where( a => dataViewPersonIdQry.Contains( a.PersonId ) ); } } // declare the qryResult that we'll use in case they didn't choose IncludeParents or IncludeChildren (and the Anonymous Type will also work if we do include parents or children) var qryPerson = personService.Queryable(); var qryResult = qryByPersonWithSummary.Join( qryPerson, a => a.PersonId, p => p.Id, ( a, p ) => new { a.PersonId, ParentId = (int?)null, ChildId = (int?)null, Person = p, Parent = (Person)null, Child = (Person)null, a.FirstVisits, a.LastVisit, p.PhoneNumbers, a.AttendanceSummary } ); var includeParents = hfViewBy.Value.ConvertToEnumOrNull<ViewBy>().GetValueOrDefault( ViewBy.Attendees ) == ViewBy.ParentsOfAttendees; var includeChildren = hfViewBy.Value.ConvertToEnumOrNull<ViewBy>().GetValueOrDefault( ViewBy.Attendees ) == ViewBy.ChildrenOfAttendees; // if Including Parents, join with qryChildWithParent instead of qryPerson if ( includeParents ) { var qryChildWithParent = new PersonService( rockContext ).GetChildWithParent(); qryResult = qryByPersonWithSummary.Join( qryChildWithParent, a => a.PersonId, p => p.Child.Id, ( a, p ) => new { a.PersonId, ParentId = (int?)p.Parent.Id, ChildId = (int?)null, Person = p.Child, Parent = p.Parent, Child = (Person)null, a.FirstVisits, a.LastVisit, p.Parent.PhoneNumbers, a.AttendanceSummary } ); } if ( includeChildren ) { var qryParentWithChildren = new PersonService( rockContext ).GetParentWithChild(); qryResult = qryByPersonWithSummary.Join( qryParentWithChildren, a => a.PersonId, p => p.Parent.Id, ( a, p ) => new { a.PersonId, ParentId = (int?)null, ChildId = (int?)p.Child.Id, Person = p.Parent, Parent = (Person)null, Child = p.Child, a.FirstVisits, a.LastVisit, p.Child.PhoneNumbers, a.AttendanceSummary } ); } var parentField = gAttendeesAttendance.Columns.OfType<PersonField>().FirstOrDefault( a => a.HeaderText == "Parent" ); if ( parentField != null ) { parentField.Visible = includeParents; } var parentEmailField = gAttendeesAttendance.Columns.OfType<RockBoundField>().FirstOrDefault( a => a.HeaderText == "Parent Email" ); if ( parentEmailField != null ) { parentEmailField.ExcelExportBehavior = includeParents ? ExcelExportBehavior.AlwaysInclude : ExcelExportBehavior.NeverInclude; } var childField = gAttendeesAttendance.Columns.OfType<PersonField>().FirstOrDefault( a => a.HeaderText == "Child" ); if ( childField != null ) { childField.Visible = includeChildren; } var childEmailField = gAttendeesAttendance.Columns.OfType<RockBoundField>().FirstOrDefault( a => a.HeaderText == "Child Email" ); if ( childEmailField != null ) { childEmailField.ExcelExportBehavior = includeChildren ? ExcelExportBehavior.AlwaysInclude : ExcelExportBehavior.NeverInclude; } SortProperty sortProperty = gAttendeesAttendance.SortProperty; if ( sortProperty != null ) { if ( sortProperty.Property == "AttendanceSummary.Count" ) { if ( sortProperty.Direction == SortDirection.Descending ) { qryResult = qryResult.OrderByDescending( a => a.AttendanceSummary.Count() ); } else { qryResult = qryResult.OrderBy( a => a.AttendanceSummary.Count() ); } } else if ( sortProperty.Property == "FirstVisit.StartDateTime" ) { if ( sortProperty.Direction == SortDirection.Descending ) { qryResult = qryResult.OrderByDescending( a => a.FirstVisits.Min() ); } else { qryResult = qryResult.OrderBy( a => a.FirstVisits.Min() ); } } else { qryResult = qryResult.Sort( sortProperty ); } } else { qryResult = qryResult.OrderBy( a => a.Person.LastName ).ThenBy( a => a.Person.NickName ); } var attendancePercentField = gAttendeesAttendance.Columns.OfType<RockTemplateField>().First( a => a.HeaderText.EndsWith( "Attendance %" ) ); attendancePercentField.HeaderText = string.Format( "{0}ly Attendance %", groupBy.ConvertToString() ); // Calculate all the possible attendance summary dates UpdatePossibleAttendances( dateRange, groupBy ); // pre-load the schedule names since FriendlyScheduleText requires building the ICal object, etc _scheduleNameLookup = new ScheduleService( rockContext ).Queryable() .ToList() .ToDictionary( k => k.Id, v => v.FriendlyScheduleText ); if ( includeParents ) { gAttendeesAttendance.PersonIdField = "ParentId"; gAttendeesAttendance.DataKeyNames = new string[] { "ParentId", "PersonId" }; } else if ( includeChildren ) { gAttendeesAttendance.PersonIdField = "ChildId"; gAttendeesAttendance.DataKeyNames = new string[] { "ChildId", "PersonId" }; } else { gAttendeesAttendance.PersonIdField = "PersonId"; gAttendeesAttendance.DataKeyNames = new string[] { "PersonId" }; } // Create the dynamic attendance grid columns as needed CreateDynamicAttendanceGridColumns(); try { nbAttendeesError.Visible = false; // increase the timeout from 30 to 90. The Query can be slow if SQL hasn't calculated the Query Plan for the query yet. // Sometimes, most of the time consumption is figuring out the Query Plan, but after it figures it out, it caches it so that the next time it'll be much faster rockContext.Database.CommandTimeout = 90; gAttendeesAttendance.SetLinqDataSource( qryResult.AsNoTracking() ); gAttendeesAttendance.DataBind(); } catch ( Exception exception ) { LogAndShowException( exception ); } }
/// <summary> /// Gets the possible attendances for the date range. /// </summary> /// <param name="dateRange">The date range.</param> /// <param name="attendanceGroupBy">The attendance group by type.</param> /// <returns></returns> public List<DateTime> GetPossibleAttendancesForDateRange( DateRange dateRange, ChartGroupBy attendanceGroupBy ) { TimeSpan dateRangeSpan = dateRange.End.Value - dateRange.Start.Value; var result = new List<DateTime>(); if ( attendanceGroupBy == ChartGroupBy.Week ) { var endOfFirstWeek = dateRange.Start.Value.EndOfWeek( RockDateTime.FirstDayOfWeek ); var endOfLastWeek = dateRange.End.Value.EndOfWeek( RockDateTime.FirstDayOfWeek ); var weekEndDate = endOfFirstWeek; while ( weekEndDate <= endOfLastWeek ) { // Weeks are summarized as the last day of the "Rock" week (Sunday) result.Add( weekEndDate ); weekEndDate = weekEndDate.AddDays( 7 ); } } else if ( attendanceGroupBy == ChartGroupBy.Month ) { var endOfFirstMonth = dateRange.Start.Value.AddDays( -( dateRange.Start.Value.Day - 1 ) ).AddMonths( 1 ).AddDays( -1 ); var endOfLastMonth = dateRange.End.Value.AddDays( -( dateRange.End.Value.Day - 1 ) ).AddMonths( 1 ).AddDays( -1 ); //// Months are summarized as the First Day of the month: For example, 5/1/2015 would include everything from 5/1/2015 - 5/31/2015 (inclusive) var monthStartDate = new DateTime( endOfFirstMonth.Year, endOfFirstMonth.Month, 1 ); while ( monthStartDate <= endOfLastMonth ) { result.Add( monthStartDate ); monthStartDate = monthStartDate.AddMonths( 1 ); } } else if ( attendanceGroupBy == ChartGroupBy.Year ) { var endOfFirstYear = new DateTime( dateRange.Start.Value.Year, 1, 1 ).AddYears( 1 ).AddDays( -1 ); var endOfLastYear = new DateTime( dateRange.End.Value.Year, 1, 1 ).AddYears( 1 ).AddDays( -1 ); //// Years are summarized as the First Day of the year: For example, 1/1/2015 would include everything from 1/1/2015 - 12/31/2015 (inclusive) var yearStartDate = new DateTime( endOfFirstYear.Year, 1, 1 ); while ( yearStartDate <= endOfLastYear ) { result.Add( yearStartDate ); yearStartDate = yearStartDate.AddYears( 1 ); } } // only include current and previous dates var currentDateTime = RockDateTime.Now; result = result.Where( a => a <= currentDateTime.Date ).ToList(); return result; }
/// <summary> /// Updates the possible attendance summary dates /// </summary> /// <param name="dateRange">The date range.</param> /// <param name="attendanceGroupBy">The attendance group by.</param> public void UpdatePossibleAttendances( DateRange dateRange, ChartGroupBy attendanceGroupBy ) { _possibleAttendances = GetPossibleAttendancesForDateRange( dateRange, attendanceGroupBy ); }
/// <summary> /// Binds the attendees grid. /// </summary> private void BindAttendeesGrid( bool isExporting = false ) { // Get Group Type filter var groupTypes = this.GetSelectedGroupTypes(); if ( groupTypes == null || !groupTypes.Any() ) { return; } var groupTypeIdList = groupTypes.Select( t => t.Id ).ToList(); // Get the daterange filter var dateRange = SlidingDateRangePicker.CalculateDateRangeFromDelimitedValues( drpSlidingDateRange.DelimitedValues ); if ( dateRange.End == null ) { dateRange.End = RockDateTime.Now; } var start = dateRange.Start; var end = dateRange.End; // Get the group filter var groupIdList = new List<int>(); string groupIds = GetSelectedGroupIds().AsDelimited( "," ); if ( !string.IsNullOrWhiteSpace( groupIds ) ) { groupIdList = groupIds.Split( ',' ).AsIntegerList(); } // If campuses were included, filter attendances by those that have selected campuses // if 'null' is one of the campuses, treat that as a 'CampusId is Null' var includeNullCampus = clbCampuses.SelectedValues.Any( a => a.Equals( "null", StringComparison.OrdinalIgnoreCase ) ); var campusIdList = clbCampuses.SelectedValues.AsIntegerList(); campusIdList.Remove( 0 ); // remove 0 from the list, just in case it is there if ( !includeNullCampus && !campusIdList.Any() ) { campusIdList = null; } // If schedules were included, filter attendance by those that have the selected schedules var scheduleIdList = spSchedules.SelectedValues.AsIntegerList(); scheduleIdList.Remove( 0 ); if ( !scheduleIdList.Any() ) { scheduleIdList = null; } // we want to get the first 2 visits at a minimum so we can show the dates in the grid int nthVisitsTake = 2; int? byNthVisit = null; if ( radByVisit.Checked ) { // If we are filtering by nth visit, we might want to get up to first 5 byNthVisit = ddlNthVisit.SelectedValue.AsIntegerOrNull(); if ( byNthVisit.HasValue && byNthVisit > 2 ) { nthVisitsTake = byNthVisit.Value; } } bool showNonAttenders = byNthVisit.HasValue && byNthVisit.Value == 0; // Get any attendance pattern filters int? attendedMinCount = null; int? attendedMissedCount = null; DateRange attendedMissedDateRange = new DateRange(); if ( radByPattern.Checked ) { attendedMinCount = tbPatternXTimes.Text.AsIntegerOrNull(); if ( cbPatternAndMissed.Checked ) { attendedMissedCount = tbPatternMissedXTimes.Text.AsIntegerOrNull(); attendedMissedDateRange = new DateRange( drpPatternDateRange.LowerValue, drpPatternDateRange.UpperValue ); if ( !attendedMissedDateRange.Start.HasValue || !attendedMissedDateRange.End.HasValue ) { nbMissedDateRangeRequired.Visible = true; return; } } } nbMissedDateRangeRequired.Visible = false; // Determine how dates shold be grouped ChartGroupBy groupBy = hfGroupBy.Value.ConvertToEnumOrNull<ChartGroupBy>() ?? ChartGroupBy.Week; // Determine if parents or children are being included with results var includeParents = hfViewBy.Value.ConvertToEnumOrNull<ViewBy>().GetValueOrDefault( ViewBy.Attendees ) == ViewBy.ParentsOfAttendees; var includeChildren = hfViewBy.Value.ConvertToEnumOrNull<ViewBy>().GetValueOrDefault( ViewBy.Attendees ) == ViewBy.ChildrenOfAttendees; // Atttendance results var allAttendeeVisits = new Dictionary<int, AttendeeVisits>(); var allResults = new List<AttendeeResult>(); // Collection of async queries to run before assembling data var qryTasks = new List<Task>(); DataTable dtAttendeeLastAttendance = null; DataTable dtAttendees = null; DataTable dtAttendeeFirstDates = null; List<int> personIdsWhoDidNotMiss = null; if ( !showNonAttenders ) { // Call the stored procedure to get all the person ids and their attendance dates for anyone // whith attendance that matches the selected criteria. qryTasks.Add( Task.Run( () => { DataTable dtAttendeeDates = AttendanceService.GetAttendanceAnalyticsAttendeeDates( groupIdList, start, end, campusIdList, includeNullCampus, scheduleIdList ).Tables[0]; foreach ( DataRow row in dtAttendeeDates.Rows ) { int personId = (int)row["PersonId"]; allAttendeeVisits.AddOrIgnore( personId, new AttendeeVisits() ); var result = allAttendeeVisits[personId]; result.PersonId = personId; DateTime summaryDate = DateTime.MinValue; switch ( groupBy ) { case ChartGroupBy.Week: summaryDate = (DateTime)row["SundayDate"]; break; case ChartGroupBy.Month: summaryDate = (DateTime)row["MonthDate"]; break; case ChartGroupBy.Year: summaryDate = (DateTime)row["YearDate"]; break; } if ( !result.AttendanceSummary.Contains( summaryDate ) ) { result.AttendanceSummary.Add( summaryDate ); } } } ) ); // Call the stored procedure to get the last attendance qryTasks.Add( Task.Run( () => { dtAttendeeLastAttendance = AttendanceService.GetAttendanceAnalyticsAttendeeLastAttendance( groupIdList, start, end, campusIdList, includeNullCampus, scheduleIdList ).Tables[0]; } ) ); // Call the stored procedure to get the names/demographic info for attendess qryTasks.Add( Task.Run( () => { dtAttendees = AttendanceService.GetAttendanceAnalyticsAttendees( groupIdList, start, end, campusIdList, includeNullCampus, scheduleIdList, includeParents, includeChildren ).Tables[0]; } ) ); // If checking for missed attendance, get the people who missed that number of dates during the missed date range if ( attendedMissedCount.HasValue && attendedMissedDateRange.Start.HasValue && attendedMissedDateRange.End.HasValue ) { qryTasks.Add( Task.Run( () => { personIdsWhoDidNotMiss = new List<int>(); DataTable dtAttendeeDatesMissed = AttendanceService.GetAttendanceAnalyticsAttendeeDates( groupIdList, attendedMissedDateRange.Start.Value, attendedMissedDateRange.End.Value, campusIdList, includeNullCampus, scheduleIdList ).Tables[0]; var missedResults = new Dictionary<int, AttendeeResult>(); foreach ( DataRow row in dtAttendeeDatesMissed.Rows ) { int personId = (int)row["PersonId"]; missedResults.AddOrIgnore( personId, new AttendeeResult() ); var missedResult = missedResults[personId]; missedResult.PersonId = personId; DateTime summaryDate = DateTime.MinValue; switch ( groupBy ) { case ChartGroupBy.Week: summaryDate = (DateTime)row["SundayDate"]; break; case ChartGroupBy.Month: summaryDate = (DateTime)row["MonthDate"]; break; case ChartGroupBy.Year: summaryDate = (DateTime)row["YearDate"]; break; } if ( !missedResult.AttendanceSummary.Contains( summaryDate ) ) { missedResult.AttendanceSummary.Add( summaryDate ); } } var missedPossibleDates = GetPossibleAttendancesForDateRange( attendedMissedDateRange, groupBy ); int missedPossibleCount = missedPossibleDates.Count(); personIdsWhoDidNotMiss = missedResults .Where( m => missedPossibleCount - m.Value.AttendanceSummary.Count < attendedMissedCount.Value ) .Select( m => m.Key ) .ToList(); } ) ); } // Call the stored procedure to get the first five dates that any person attended this group type qryTasks.Add( Task.Run( () => { dtAttendeeFirstDates = AttendanceService.GetAttendanceAnalyticsAttendeeFirstDates( groupTypeIdList, groupIdList, start, end, campusIdList, includeNullCampus, scheduleIdList ).Tables[0]; } ) ); } else { qryTasks.Add( Task.Run( () => { DataSet ds = AttendanceService.GetAttendanceAnalyticsNonAttendees( groupTypeIdList, groupIdList, start, end, campusIdList, includeNullCampus, scheduleIdList, includeParents, includeChildren ); DataTable dtNonAttenders = ds.Tables[0]; dtAttendeeFirstDates = ds.Tables[1]; dtAttendeeLastAttendance = ds.Tables[2]; foreach ( DataRow row in dtNonAttenders.Rows ) { int personId = (int)row["Id"]; var result = new AttendeeResult(); result.PersonId = personId; var person = new PersonInfo(); person.NickName = row["NickName"].ToString(); person.LastName = row["LastName"].ToString(); person.Email = row["Email"].ToString(); person.Birthdate = row["BirthDate"] as DateTime?; person.Age = Person.GetAge( person.Birthdate ); person.ConnectionStatusValueId = row["ConnectionStatusValueId"] as int?; result.Person = person; if ( includeParents ) { result.ParentId = (int)row["ParentId"]; var parent = new PersonInfo(); parent.NickName = row["ParentNickName"].ToString(); parent.LastName = row["ParentLastName"].ToString(); parent.Email = row["ParentEmail"].ToString(); parent.Birthdate = row["ParentBirthDate"] as DateTime?; parent.Age = Person.GetAge( parent.Birthdate ); result.Parent = parent; } if ( includeChildren ) { var child = new PersonInfo(); result.ChildId = (int)row["ChildId"]; child.NickName = row["ChildNickName"].ToString(); child.LastName = row["ChildLastName"].ToString(); child.Email = row["ChildEmail"].ToString(); child.Birthdate = row["ChildBirthDate"] as DateTime?; child.Age = Person.GetAge( child.Birthdate ); result.Child = child; } allResults.Add( result ); } } ) ); } // If a dataview filter was included, find the people who match that criteria List<int> dataViewPersonIds = null; qryTasks.Add( Task.Run( () => { var dataViewId = dvpDataView.SelectedValueAsInt(); if ( dataViewId.HasValue ) { dataViewPersonIds = new List<int>(); var dataView = new DataViewService( _rockContext ).Get( dataViewId.Value ); if ( dataView != null ) { var errorMessages = new List<string>(); var dvPersonService = new PersonService( _rockContext ); ParameterExpression paramExpression = dvPersonService.ParameterExpression; Expression whereExpression = dataView.GetExpression( dvPersonService, paramExpression, out errorMessages ); SortProperty sort = null; var dataViewPersonIdQry = dvPersonService .Queryable().AsNoTracking() .Where( paramExpression, whereExpression, sort ) .Select( p => p.Id ); dataViewPersonIds = dataViewPersonIdQry.ToList(); } } } ) ); // Wait for all the queries to finish Task.WaitAll( qryTasks.ToArray() ); if ( !showNonAttenders ) { var attendees = allAttendeeVisits.AsQueryable(); // If dataview filter was included remove anyone not in that dataview if ( dataViewPersonIds != null ) { attendees = attendees.Where( p => dataViewPersonIds.Contains( p.Key ) ); } // If filter for number missed was included, remove anyone who did not match that filter if ( personIdsWhoDidNotMiss != null ) { attendees = attendees.Where( p => !personIdsWhoDidNotMiss.Contains( p.Key ) ); } // If filtering by minimum times attended if ( attendedMinCount.HasValue ) { attendees = attendees.Where( p => p.Value.AttendanceSummary.Count() >= attendedMinCount ); } // Force filter application allAttendeeVisits = attendees.ToDictionary( k => k.Key, v => v.Value ); // Add the First Visit information foreach ( DataRow row in dtAttendeeFirstDates.Rows ) { int personId = (int)row["PersonId"]; if ( allAttendeeVisits.ContainsKey( personId ) ) { allAttendeeVisits[personId].FirstVisits.Add( (DateTime)row["StartDate"] ); } } // If filtering based on visit time, only include those who visited the selected time during the date range if ( byNthVisit.HasValue ) { int skipCount = byNthVisit.Value - 1; allAttendeeVisits = allAttendeeVisits .Where( p => p.Value.FirstVisits.Skip( skipCount ).Take( 1 ).Any( d => d >= start && d < end ) ) .ToDictionary( k => k.Key, v => v.Value ); } // Add the Last Attended information if ( dtAttendeeLastAttendance != null ) { foreach ( DataRow row in dtAttendeeLastAttendance.Rows ) { int personId = (int)row["PersonId"]; if ( allAttendeeVisits.ContainsKey( personId ) ) { var result = allAttendeeVisits[personId]; if ( result.LastVisit == null ) { var lastAttendance = new PersonLastAttendance(); lastAttendance.CampusId = row["CampusId"] as int?; lastAttendance.GroupId = row["GroupId"] as int?; lastAttendance.GroupName = row["GroupName"].ToString(); lastAttendance.RoleName = row["RoleName"].ToString(); lastAttendance.InGroup = !string.IsNullOrWhiteSpace( lastAttendance.RoleName ); lastAttendance.ScheduleId = row["ScheduleId"] as int?; lastAttendance.StartDateTime = (DateTime)row["StartDateTime"]; lastAttendance.LocationId = row["LocationId"] as int?; lastAttendance.LocationName = row["LocationName"].ToString(); result.LastVisit = lastAttendance; } } } } // Add the Demographic information if ( dtAttendees != null ) { var newResults = new Dictionary<int, AttendeeResult>(); foreach ( DataRow row in dtAttendees.Rows ) { int personId = (int)row["Id"]; if ( allAttendeeVisits.ContainsKey( personId ) ) { var result = new AttendeeResult( allAttendeeVisits[personId] ); var person = new PersonInfo(); person.NickName = row["NickName"].ToString(); person.LastName = row["LastName"].ToString(); person.Email = row["Email"].ToString(); person.Birthdate = row["BirthDate"] as DateTime?; person.Age = Person.GetAge( person.Birthdate ); person.ConnectionStatusValueId = row["ConnectionStatusValueId"] as int?; result.Person = person; if ( includeParents ) { result.ParentId = (int)row["ParentId"]; var parent = new PersonInfo(); parent.NickName = row["ParentNickName"].ToString(); parent.LastName = row["ParentLastName"].ToString(); parent.Email = row["ParentEmail"].ToString(); parent.Birthdate = row["ParentBirthDate"] as DateTime?; parent.Age = Person.GetAge( parent.Birthdate ); result.Parent = parent; } if ( includeChildren ) { var child = new PersonInfo(); result.ChildId = (int)row["ChildId"]; child.NickName = row["ChildNickName"].ToString(); child.LastName = row["ChildLastName"].ToString(); child.Email = row["ChildEmail"].ToString(); child.Birthdate = row["ChildBirthDate"] as DateTime?; child.Age = Person.GetAge( child.Birthdate ); result.Child = child; } allResults.Add( result ); } } } } else { // If dataview filter was included remove anyone not in that dataview if ( dataViewPersonIds != null ) { allResults = allResults .Where( p => dataViewPersonIds.Contains( p.PersonId ) ) .ToList(); } // Add the first visit dates for people foreach ( DataRow row in dtAttendeeFirstDates.Rows ) { int personId = (int)row["PersonId"]; foreach ( var result in allResults.Where( r => r.PersonId == personId ) ) { result.FirstVisits.Add( (DateTime)row["StartDate"] ); } } // Add the Last Attended information if ( dtAttendeeLastAttendance != null ) { foreach ( DataRow row in dtAttendeeLastAttendance.Rows ) { int personId = (int)row["PersonId"]; foreach ( var result in allResults.Where( r => r.PersonId == personId ) ) { if ( result.LastVisit == null ) { var lastAttendance = new PersonLastAttendance(); lastAttendance.CampusId = row["CampusId"] as int?; lastAttendance.GroupId = row["GroupId"] as int?; lastAttendance.GroupName = row["GroupName"].ToString(); lastAttendance.RoleName = row["RoleName"].ToString(); lastAttendance.InGroup = !string.IsNullOrWhiteSpace( lastAttendance.RoleName ); lastAttendance.ScheduleId = row["ScheduleId"] as int?; lastAttendance.StartDateTime = (DateTime)row["StartDateTime"]; lastAttendance.LocationId = row["LocationId"] as int?; lastAttendance.LocationName = row["LocationName"].ToString(); result.LastVisit = lastAttendance; } } } } } // Begin formatting the columns var qryResult = allResults.AsQueryable(); var personUrlFormatString = ( (RockPage)this.Page ).ResolveRockUrl( "~/Person/{0}" ); var personHyperLinkField = gAttendeesAttendance.Columns.OfType<HyperLinkField>().FirstOrDefault( a => a.HeaderText == "Name" ); if ( personHyperLinkField != null ) { personHyperLinkField.DataNavigateUrlFormatString = personUrlFormatString; } var parentHyperLinkField = gAttendeesAttendance.Columns.OfType<HyperLinkField>().FirstOrDefault( a => a.HeaderText == "Parent" ); if ( parentHyperLinkField != null ) { parentHyperLinkField.Visible = includeParents; parentHyperLinkField.DataNavigateUrlFormatString = personUrlFormatString; } var parentField = gAttendeesAttendance.Columns.OfType<RockBoundField>().FirstOrDefault( a => a.HeaderText == "Parent" ); if ( parentField != null ) { parentField.ExcelExportBehavior = includeParents ? ExcelExportBehavior.AlwaysInclude : ExcelExportBehavior.NeverInclude; } var parentEmailField = gAttendeesAttendance.Columns.OfType<RockBoundField>().FirstOrDefault( a => a.HeaderText == "Parent Email" ); if ( parentEmailField != null ) { parentEmailField.ExcelExportBehavior = includeParents ? ExcelExportBehavior.AlwaysInclude : ExcelExportBehavior.NeverInclude; } var childHyperLinkField = gAttendeesAttendance.Columns.OfType<HyperLinkField>().FirstOrDefault( a => a.HeaderText == "Child" ); if ( childHyperLinkField != null ) { childHyperLinkField.Visible = includeChildren; childHyperLinkField.DataNavigateUrlFormatString = personUrlFormatString; } var childfield = gAttendeesAttendance.Columns.OfType<RockBoundField>().FirstOrDefault( a => a.HeaderText == "Child" ); if ( childfield != null ) { childfield.ExcelExportBehavior = includeChildren ? ExcelExportBehavior.AlwaysInclude : ExcelExportBehavior.NeverInclude; } var childEmailField = gAttendeesAttendance.Columns.OfType<RockBoundField>().FirstOrDefault( a => a.HeaderText == "Child Email" ); if ( childEmailField != null ) { childEmailField.ExcelExportBehavior = includeChildren ? ExcelExportBehavior.AlwaysInclude : ExcelExportBehavior.NeverInclude; } var childAgeField = gAttendeesAttendance.Columns.OfType<RockBoundField>().FirstOrDefault( a => a.HeaderText == "Child Age" ); if ( childAgeField != null ) { childAgeField.ExcelExportBehavior = includeChildren ? ExcelExportBehavior.AlwaysInclude : ExcelExportBehavior.NeverInclude; } SortProperty sortProperty = gAttendeesAttendance.SortProperty; if ( sortProperty != null ) { if ( sortProperty.Property == "AttendanceSummary.Count" ) { if ( sortProperty.Direction == SortDirection.Descending ) { qryResult = qryResult.OrderByDescending( a => a.AttendanceSummary.Count() ); } else { qryResult = qryResult.OrderBy( a => a.AttendanceSummary.Count() ); } } else if ( sortProperty.Property == "FirstVisit.StartDateTime" ) { if ( sortProperty.Direction == SortDirection.Descending ) { qryResult = qryResult.OrderByDescending( a => a.FirstVisits.Min() ); } else { qryResult = qryResult.OrderBy( a => a.FirstVisits.Min() ); } } else { qryResult = qryResult.Sort( sortProperty ); } } else { qryResult = qryResult.OrderBy( a => a.Person.LastName ).ThenBy( a => a.Person.NickName ); } var attendancePercentField = gAttendeesAttendance.Columns.OfType<RockTemplateField>().First( a => a.HeaderText.EndsWith( "Attendance %" ) ); attendancePercentField.HeaderText = string.Format( "{0}ly Attendance %", groupBy.ConvertToString() ); // Calculate all the possible attendance summary dates UpdatePossibleAttendances( dateRange, groupBy ); // pre-load the schedule names since FriendlyScheduleText requires building the ICal object, etc _scheduleNameLookup = new ScheduleService( _rockContext ).Queryable() .ToList() .ToDictionary( k => k.Id, v => v.FriendlyScheduleText ); if ( includeParents ) { gAttendeesAttendance.PersonIdField = "ParentId"; gAttendeesAttendance.DataKeyNames = new string[] { "ParentId", "PersonId" }; } else if ( includeChildren ) { gAttendeesAttendance.PersonIdField = "ChildId"; gAttendeesAttendance.DataKeyNames = new string[] { "ChildId", "PersonId" }; } else { gAttendeesAttendance.PersonIdField = "PersonId"; gAttendeesAttendance.DataKeyNames = new string[] { "PersonId" }; } // Create the dynamic attendance grid columns as needed CreateDynamicAttendanceGridColumns(); try { nbAttendeesError.Visible = false; gAttendeesAttendance.SetLinqDataSource( qryResult ); var currentPageItems = gAttendeesAttendance.DataSource as List<AttendeeResult>; if ( currentPageItems != null ) { var currentPagePersonIds = new List<int>(); if ( includeParents ) { currentPagePersonIds = currentPageItems.Select( i => i.ParentId ).ToList(); gAttendeesAttendance.PersonIdField = "ParentId"; gAttendeesAttendance.DataKeyNames = new string[] { "ParentId", "PersonId" }; } else if ( includeChildren ) { currentPagePersonIds = currentPageItems.Select( i => i.ChildId ).ToList(); gAttendeesAttendance.PersonIdField = "ChildId"; gAttendeesAttendance.DataKeyNames = new string[] { "ChildId", "PersonId" }; } else { currentPagePersonIds = currentPageItems.Select( i => i.PersonId ).ToList(); gAttendeesAttendance.PersonIdField = "PersonId"; gAttendeesAttendance.DataKeyNames = new string[] { "PersonId" }; } LoadCurrentPageObjects( currentPagePersonIds ); } _currentlyExporting = isExporting; gAttendeesAttendance.DataBind(); _currentlyExporting = false; } catch ( Exception exception ) { LogAndShowException( exception ); } }