/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public virtual void Execute(IJobExecutionContext context) { JobDataMap dataMap = context.JobDetail.JobDataMap; int expirationDays = dataMap.GetInt("ExpirationPeriod"); int delayMinutes = dataMap.GetInt("DelayPeriod"); var rockContext = new RockContext(); var qry = new CommunicationService(rockContext) .GetQueued(expirationDays, delayMinutes, false, false) .OrderBy(c => c.Id); var exceptionMsgs = new List <string>(); int communicationsSent = 0; foreach (var comm in qry.AsNoTracking().ToList()) { try { Rock.Model.Communication.Send(comm); communicationsSent++; } catch (Exception ex) { exceptionMsgs.Add(string.Format("Exception occurred sending communication ID:{0}:{1} {2}", comm.Id, Environment.NewLine, ex.Messages().AsDelimited(Environment.NewLine + " "))); ExceptionLogService.LogException(ex, System.Web.HttpContext.Current); } } if (communicationsSent > 0) { context.Result = string.Format("Sent {0} {1}", communicationsSent, "communication".PluralizeIf(communicationsSent > 1)); } else { context.Result = "No communications to send"; } if (exceptionMsgs.Any()) { throw new Exception("One or more exceptions occurred sending communications..." + Environment.NewLine + exceptionMsgs.AsDelimited(Environment.NewLine)); } // check for communications that have not been sent but are past the expire date. Mark them as failed and set a warning. var beginWindow = RockDateTime.Now.AddDays(0 - expirationDays); var qryExpiredRecipients = new CommunicationRecipientService(rockContext).Queryable() .Where(cr => cr.Communication.Status == CommunicationStatus.Approved && cr.Status == CommunicationRecipientStatus.Pending && ( (!cr.Communication.FutureSendDateTime.HasValue && cr.Communication.CreatedDateTime.HasValue && cr.Communication.CreatedDateTime < beginWindow) || (cr.Communication.FutureSendDateTime.HasValue && cr.Communication.FutureSendDateTime < beginWindow) )); var count = qryExpiredRecipients.Count(); rockContext.BulkUpdate(qryExpiredRecipients, c => new CommunicationRecipient { Status = CommunicationRecipientStatus.Failed, StatusNote = "Communication was not sent before the expire window (possibly due to delayed approval)." }); }
/// <summary> /// Updates the step set with step program completion /// </summary> public static void UpdateStepProgramCompletion(List <Step> stepSet, int personAliasId, int stepProgramId, RockContext rockContext = null) { rockContext = rockContext ?? new RockContext(); var campusId = stepSet.Where(a => a.CampusId.HasValue).Select(a => a.CampusId).FirstOrDefault(); var startDateTime = stepSet.Select(a => a.StartDateTime ?? a.CreatedDateTime).OrderBy(a => a).FirstOrDefault(); var endDateTime = stepSet.Select(a => a.CompletedDateTime ?? a.EndDateTime).OrderByDescending(a => a).FirstOrDefault(); var stepProgramCompletionService = new StepProgramCompletionService(rockContext); var stepService = new StepService(rockContext); var stepProgramCompletion = new StepProgramCompletion { StepProgramId = stepProgramId, PersonAliasId = personAliasId, CampusId = campusId, StartDateTime = startDateTime.Value, EndDateTime = endDateTime }; stepProgramCompletionService.Add(stepProgramCompletion); rockContext.SaveChanges(); var stepIds = stepSet.Select(b => b.Id); var stepQry = stepService.Queryable().Where(a => stepIds.Contains(a.Id)); rockContext.BulkUpdate(stepQry, a => new Step { StepProgramCompletionId = stepProgramCompletion.Id }); }
/// <summary> /// Executes this instance. /// </summary> /// <param name="message"></param> public override void Execute(Message message) { var pageCache = PageCache.Get(message.PageGuid); if (pageCache == null) { return; } var rockContext = new RockContext(); var interactionComponentService = new InteractionComponentService(rockContext); var componentQuery = interactionComponentService.QueryByPage(pageCache); rockContext.BulkUpdate(componentQuery, ic => new InteractionComponent { Name = pageCache.InternalName }); }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public void Execute(IJobExecutionContext context) { var familyGroupTypeId = GroupTypeCache.GetFamilyGroupType().Id; var rockContext = new RockContext(); // just in case there are Groups that have a null or empty Name, update them. var familiesWithoutNames = new GroupService(rockContext) .Queryable().Where(a => a.GroupTypeId == familyGroupTypeId) .Where(a => string.IsNullOrEmpty(a.Name)); if (familiesWithoutNames.Any()) { rockContext.BulkUpdate(familiesWithoutNames, g => new Group { Name = "Family" }); } // Re-calculates all GroupSalutation values on Family Groups. var familyIdList = new GroupService(rockContext) .Queryable().Where(a => a.GroupTypeId == familyGroupTypeId) .Select(a => a.Id).ToList(); foreach (var familyId in familyIdList) { try { using (var rockContextUpdate = new RockContext()) { GroupService.UpdateGroupSalutations(familyId, rockContextUpdate); } } catch (Exception ex) { ExceptionLogService.LogException(new Exception($"Error running the job 'PostV127DataMigrationsRebuildGroupSalutations'. UpdateGroupSalutations failed for Group Id {familyId}", ex)); } } ServiceJobService.DeleteJob(context.GetJobId()); }
/// <summary> /// Sends the note watch notifications. /// </summary> /// <param name="context">The context.</param> private List <string> SendNoteWatchNotifications(IJobExecutionContext context) { var errors = new List <string>(); List <int> noteIdsToProcessNoteWatchesList = new List <int>(); using (var rockContext = new RockContext()) { var noteService = new NoteService(rockContext); var noteWatchService = new NoteWatchService(rockContext); var noteWatchQuery = noteWatchService.Queryable(); if (!noteWatchQuery.Any()) { // there aren't any note watches, so there is nothing to do return(errors); } // get all notes that haven't processed notifications yet var notesToNotifyQuery = noteService.Queryable().Where(a => a.NotificationsSent == false && a.NoteType.AllowsWatching == true && a.EditedDateTime > _cutoffNoteEditDateTime); // limit to notes that don't require approval or are approved notesToNotifyQuery = notesToNotifyQuery.Where(a => a.NoteType.RequiresApprovals == false || a.ApprovalStatus == NoteApprovalStatus.Approved); if (!notesToNotifyQuery.Any()) { // there aren't any notes that haven't had notifications processed yet return(errors); } noteIdsToProcessNoteWatchesList = notesToNotifyQuery.Select(a => a.Id).ToList(); } // make a list of notifications to send to each personId Dictionary <int, NoteWatchPersonToNotifyList> personNotificationDigestList = new Dictionary <int, NoteWatchPersonToNotifyList>(); using (var rockContext = new RockContext()) { foreach (int noteId in noteIdsToProcessNoteWatchesList) { this.UpdateNoteWatchNotificationDigest(personNotificationDigestList, rockContext, noteId); } // Send NoteWatch notifications if (personNotificationDigestList.Any()) { foreach (var personNotificationDigest in personNotificationDigestList) { var recipients = new List <RecipientData>(); Person personToNotify = personNotificationDigest.Value.Person; List <Note> noteList = personNotificationDigest.Value.Select(a => a.Note).OrderBy(a => a.EditedDateTime).ToList(); // make sure a person doesn't get a notification on a note that they wrote noteList = noteList.Where(a => a.EditedByPersonAlias?.PersonId != personToNotify.Id).ToList(); if (!string.IsNullOrEmpty(personToNotify.Email) && personToNotify.IsEmailActive && personToNotify.EmailPreference != EmailPreference.DoNotEmail && noteList.Any()) { var mergeFields = new Dictionary <string, object>(_defaultMergeFields); mergeFields.Add("Person", personToNotify); mergeFields.Add("NoteList", noteList); recipients.Add(new RecipientData(personToNotify.Email, mergeFields)); } if (_noteWatchNotificationEmailGuid.HasValue) { var emailMessage = new RockEmailMessage(_noteWatchNotificationEmailGuid.Value); emailMessage.SetRecipients(recipients); emailMessage.Send(out errors); _noteWatchNotificationsSent += recipients.Count(); } } } } using (var rockUpdateContext = new RockContext()) { var notesToMarkNotified = new NoteService(rockUpdateContext).Queryable().Where(a => noteIdsToProcessNoteWatchesList.Contains(a.Id)); // use BulkUpdate to mark all the notes that we processed to NotificationsSent = true rockUpdateContext.BulkUpdate(notesToMarkNotified, n => new Note { NotificationsSent = true }); } return(errors); }
/// <summary> /// Sends the note approval notifications. /// </summary> /// <param name="context">The context.</param> private List <string> SendNoteApprovalNotifications(IJobExecutionContext context) { var errors = new List <string>(); List <int> noteIdsToProcessApprovalsList = new List <int>(); using (var rockContext = new RockContext()) { var noteService = new Rock.Model.NoteService(rockContext); // get all notes that are pending approval and haven't sent approval notifications yet var notesThatNeedApprovalNotifyQuery = noteService.Queryable().Where(a => a.NoteType.RequiresApprovals && a.NoteType.SendApprovalNotifications && a.ApprovalsSent == false && a.ApprovalStatus == NoteApprovalStatus.PendingApproval && a.EditedDateTime > _cutoffNoteEditDateTime); if (!notesThatNeedApprovalNotifyQuery.Any()) { // there aren't any notes that haven't had approval notifications processed yet return(errors); } noteIdsToProcessApprovalsList = notesThatNeedApprovalNotifyQuery.Select(a => a.Id).ToList(); } using (var rockContext = new RockContext()) { // get the approvers for each notetypeId Dictionary <int, List <Person> > noteTypeApprovers = new Dictionary <int, List <Person> >(); NoteTypeService noteTypeService = new NoteTypeService(rockContext); var noteTypeIdsForNotes = new NoteService(rockContext).Queryable() .Where(a => noteIdsToProcessApprovalsList.Contains(a.Id)).Select(a => a.NoteTypeId).Distinct().ToList(); foreach (var noteTypeId in noteTypeIdsForNotes) { var approvers = noteTypeService.GetApprovers(noteTypeId).ToList(); noteTypeApprovers.Add(noteTypeId, approvers); } // make a list of notes for each approver so we can send a digest of notes to approve to each approver Dictionary <Person, List <Note> > approverNotesToApproveList = new Dictionary <Person, List <Note> >(); foreach (var noteId in noteIdsToProcessApprovalsList) { var noteService = new Rock.Model.NoteService(rockContext); var note = noteService.Get(noteId); var approversForNote = noteTypeApprovers.GetValueOrNull(note.NoteTypeId); if (approversForNote?.Any() == true) { List <Note> notesToApprove; foreach (Person approverPerson in approversForNote) { if (approverNotesToApproveList.ContainsKey(approverPerson)) { notesToApprove = approverNotesToApproveList[approverPerson] ?? new List <Note>(); } else { notesToApprove = new List <Note>(); approverNotesToApproveList.Add(approverPerson, notesToApprove); } notesToApprove.Add(note); } } else { // if there are no approvers for this note type, leave it as pending approval } } if (!approverNotesToApproveList.Any()) { // nothing to do so exit return(errors); } // send approval emails var recipients = new List <RecipientData>(); foreach (var approverNotesToApprove in approverNotesToApproveList) { Person approverPerson = approverNotesToApprove.Key; List <Note> noteList = approverNotesToApprove.Value; if (!string.IsNullOrEmpty(approverPerson.Email) && approverPerson.IsEmailActive && noteList.Any()) { var mergeFields = new Dictionary <string, object>(_defaultMergeFields); mergeFields.Add("ApproverPerson", approverPerson); mergeFields.Add("NoteList", noteList); recipients.Add(new RecipientData(approverPerson.Email, mergeFields)); } if (_noteApprovalNotificationEmailGuid.HasValue) { var emailMessage = new RockEmailMessage(_noteApprovalNotificationEmailGuid.Value); emailMessage.SetRecipients(recipients); emailMessage.Send(out errors); _noteApprovalNotificationsSent += recipients.Count(); using (var rockUpdateContext = new RockContext()) { var noteListIds = noteList.Select(a => a.Id).ToList(); var notesToMarkApprovalSent = new NoteService(rockUpdateContext).Queryable().Where(a => noteListIds.Contains(a.Id)); // use BulkUpdate to mark all the notes that we processed to ApprovalsSent = true rockUpdateContext.BulkUpdate(notesToMarkApprovalSent, n => new Note { ApprovalsSent = true }); } } } } return(errors); }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public virtual void Execute(IJobExecutionContext context) { JobDataMap dataMap = context.JobDetail.JobDataMap; int expirationDays = dataMap.GetInt("ExpirationPeriod"); int delayMinutes = dataMap.GetInt("DelayPeriod"); int maxParallelization = dataMap.GetInt("ParallelCommunications"); List <Model.Communication> sendCommunications = null; var stopWatch = Stopwatch.StartNew(); using (var rockContext = new RockContext()) { sendCommunications = new CommunicationService(rockContext) .GetQueued(expirationDays, delayMinutes, false, false) .AsNoTracking() .ToList() .OrderBy(c => c.Id) .ToList(); } RockLogger.Log.Information(RockLogDomains.Jobs, "{0}: Queued communication query runtime: {1} ms", nameof(SendCommunications), stopWatch.ElapsedMilliseconds); if (sendCommunications == null) { context.Result = "No communications to send"; } var exceptionMsgs = new List <string>(); int communicationsSent = 0; stopWatch = Stopwatch.StartNew(); var sendCommunicationTasks = new List <Task <SendCommunicationAsyncResult> >(); RockLogger.Log.Debug(RockLogDomains.Jobs, "{0}: Send communications {1} communications.", nameof(SendCommunications), sendCommunicationTasks.Count); using (var mutex = new SemaphoreSlim(maxParallelization)) { for (var i = 0; i < sendCommunications.Count(); i++) { mutex.Wait(); var comm = sendCommunications[i]; sendCommunicationTasks.Add(Task.Run <SendCommunicationAsyncResult>(async() => await SendCommunicationAsync(comm, mutex).ConfigureAwait(false))); } /* * Now that we have fired off all of the task, we need to wait for them to complete, get their results, * and then process that result. Once all of the task have been completed we can continue. */ while (sendCommunicationTasks.Count > 0) { // Wait for a task to complete using WhenAny and then return the completed task. Since we are not running in an asynchronous method we need to use RunSync. var completedTask = AsyncHelper.RunSync(() => Task.WhenAny <SendCommunicationAsyncResult>(sendCommunicationTasks.ToArray())); // Get and process the result of the completed task. var communicationResult = completedTask.Result; if (communicationResult.Exception != null) { var agException = communicationResult.Exception as AggregateException; if (agException == null) { exceptionMsgs.Add($"Exception occurred sending communication ID:{communicationResult.Communication.Id}:{Environment.NewLine} {communicationResult.Exception.Messages().AsDelimited( Environment.NewLine + " " )}"); } else { var allExceptions = agException.Flatten(); foreach (var ex in allExceptions.InnerExceptions) { exceptionMsgs.Add($"Exception occurred sending communication ID:{communicationResult.Communication.Id}:{Environment.NewLine} {ex.Messages().AsDelimited( Environment.NewLine + " " )}"); } } ExceptionLogService.LogException(communicationResult.Exception, System.Web.HttpContext.Current); } else { communicationsSent++; } sendCommunicationTasks.Remove(completedTask); } } RockLogger.Log.Information(RockLogDomains.Jobs, "{0}: Send communications runtime: {1} ms", nameof(SendCommunications), stopWatch.ElapsedMilliseconds); if (communicationsSent > 0) { context.Result = string.Format("Sent {0} {1}", communicationsSent, "communication".PluralizeIf(communicationsSent > 1)); } else { context.Result = "No communications to send"; } if (exceptionMsgs.Any()) { throw new Exception("One or more exceptions occurred sending communications..." + Environment.NewLine + exceptionMsgs.AsDelimited(Environment.NewLine)); } // check for communications that have not been sent but are past the expire date. Mark them as failed and set a warning. var expireDateTimeEndWindow = RockDateTime.Now.AddDays(0 - expirationDays); // limit the query to only look a week prior to the window to avoid performance issue (it could be slow to query at ALL the communication recipient before the expire date, as there could several years worth ) var expireDateTimeBeginWindow = expireDateTimeEndWindow.AddDays(-7); stopWatch = Stopwatch.StartNew(); using (var rockContext = new RockContext()) { var qryExpiredRecipients = new CommunicationRecipientService(rockContext).Queryable() .Where(cr => cr.Communication.Status == CommunicationStatus.Approved && cr.Status == CommunicationRecipientStatus.Pending && ( (!cr.Communication.FutureSendDateTime.HasValue && cr.Communication.ReviewedDateTime.HasValue && cr.Communication.ReviewedDateTime <expireDateTimeEndWindow && cr.Communication.ReviewedDateTime> expireDateTimeBeginWindow) || (cr.Communication.FutureSendDateTime.HasValue && cr.Communication.FutureSendDateTime <expireDateTimeEndWindow && cr.Communication.FutureSendDateTime> expireDateTimeBeginWindow) )); rockContext.BulkUpdate(qryExpiredRecipients, c => new CommunicationRecipient { Status = CommunicationRecipientStatus.Failed, StatusNote = "Communication was not sent before the expire window (possibly due to delayed approval)." }); } RockLogger.Log.Information(RockLogDomains.Jobs, "{0}: Query expired communications runtime: {1} ms", nameof(SendCommunications), stopWatch.ElapsedMilliseconds); }
/// <summary> /// Updates Group Historical for any groups that have data group history enabled /// </summary> /// <param name="context">The context.</param> public void UpdateGroupHistorical(IJobExecutionContext context) { var rockContext = new RockContext(); var groupHistoricalService = new GroupHistoricalService(rockContext); var groupService = new GroupService(rockContext); // Note that this query utilizes .AsNoFilter() to avoid having archived groups filtered out by the GroupConfiguration class. var groupsWithHistoryEnabledQuery = groupService.AsNoFilter() .Where(a => a.GroupType.EnableGroupHistory == true) .AsNoTracking(); var groupHistoricalsCurrentQuery = groupHistoricalService.Queryable().Where(a => a.CurrentRowIndicator == true).AsNoTracking(); // Mark GroupHistorical Rows as History ( CurrentRowIndicator = false, etc ) if any of the tracked field values change var groupHistoricalNoLongerCurrent = groupHistoricalsCurrentQuery.Join( groupsWithHistoryEnabledQuery, gh => gh.GroupId, g => g.Id, (gh, g) => new { Group = g, GroupHistorical = gh }) .Where(a => a.Group.Name != a.GroupHistorical.GroupName || a.Group.GroupType.Name != a.GroupHistorical.GroupTypeName || a.Group.CampusId != a.GroupHistorical.CampusId || a.Group.ParentGroupId != a.GroupHistorical.ParentGroupId || a.Group.ScheduleId != a.GroupHistorical.ScheduleId || (a.Group.ScheduleId.HasValue && (a.Group.Schedule.ModifiedDateTime != a.GroupHistorical.ScheduleModifiedDateTime)) || a.Group.Description != a.GroupHistorical.Description || a.Group.StatusValueId != a.GroupHistorical.StatusValueId || a.Group.IsArchived != a.GroupHistorical.IsArchived || a.Group.ArchivedDateTime != a.GroupHistorical.ArchivedDateTime || a.Group.ArchivedByPersonAliasId != a.GroupHistorical.ArchivedByPersonAliasId || a.Group.IsActive != a.GroupHistorical.IsActive || a.Group.InactiveDateTime != a.GroupHistorical.InactiveDateTime ).Select(a => a.GroupHistorical).AsNoTracking(); var effectiveExpireDateTime = RockDateTime.Now; int groupsLoggedToHistory = 0; int groupsSaveToHistoryCurrent = 0; if (groupHistoricalNoLongerCurrent.Any()) { groupsLoggedToHistory = rockContext.BulkUpdate(groupHistoricalNoLongerCurrent, gh => new GroupHistorical { CurrentRowIndicator = false, ExpireDateTime = effectiveExpireDateTime }); } // Insert Groups (that have GroupType.EnableGroupHistory) that don't have a CurrentRowIndicator row yet ( or don't have a CurrentRowIndicator because it was stamped with CurrentRowIndicator=false ) var groupsToAddToHistoricalCurrentsQuery = groupsWithHistoryEnabledQuery.Where(g => !groupHistoricalsCurrentQuery.Any(gh => gh.GroupId == g.Id)).AsNoTracking(); if (groupsToAddToHistoricalCurrentsQuery.Any()) { List <GroupHistorical> groupHistoricalCurrentsToInsert = groupsToAddToHistoricalCurrentsQuery .Include(a => a.GroupType) .Include(a => a.Schedule) .ToList() .Select(g => GroupHistorical.CreateCurrentRowFromGroup(g, effectiveExpireDateTime)).ToList(); groupsSaveToHistoryCurrent = groupHistoricalCurrentsToInsert.Count(); rockContext.BulkInsert(groupHistoricalCurrentsToInsert); } if (groupsLoggedToHistory > 0) { _jobStatusMessages.Add($"Logged {groupsLoggedToHistory} {"group history snapshot".PluralizeIf( groupsLoggedToHistory != 0 )}"); } if (groupsSaveToHistoryCurrent > 0) { int newGroupsAddedToHistory = groupsSaveToHistoryCurrent - groupsLoggedToHistory; if (newGroupsAddedToHistory > 0) { _jobStatusMessages.Add($"Added {newGroupsAddedToHistory} new {"group history snapshot".PluralizeIf( newGroupsAddedToHistory != 0 )}"); } } }
/// <summary> /// Updates GroupLocationHistorical for any group locations in groups that have data group history enabled /// </summary> /// <param name="context">The context.</param> public void UpdateGroupLocationHistorical(IJobExecutionContext context) { var rockContext = new RockContext(); var groupLocationHistoricalService = new GroupLocationHistoricalService(rockContext); var groupLocationService = new GroupLocationService(rockContext); var groupLocationsWithHistoryEnabledQuery = groupLocationService.Queryable().Where(a => a.Group.GroupType.EnableGroupHistory == true).AsNoTracking(); var groupLocationsHistoricalCurrentQuery = groupLocationHistoricalService.Queryable().Where(a => a.CurrentRowIndicator == true).AsNoTracking(); // Mark GroupLocationHistorical Rows as History ( CurrentRowIndicator = false, etc ) if any of the tracked field values change var groupLocationHistoricalNoLongerCurrentQuery = groupLocationsHistoricalCurrentQuery.Join( groupLocationsWithHistoryEnabledQuery, glh => glh.GroupLocationId, gl => gl.Id, (glh, gl) => new { GroupLocation = gl, GroupLocationHistorical = glh }) .Where(a => a.GroupLocation.GroupId != a.GroupLocationHistorical.GroupId || (a.GroupLocation.GroupLocationTypeValueId != a.GroupLocation.GroupLocationTypeValueId) || (a.GroupLocation.GroupLocationTypeValueId.HasValue && a.GroupLocation.GroupLocationTypeValue.Value != a.GroupLocationHistorical.GroupLocationTypeName) || a.GroupLocation.LocationId != a.GroupLocationHistorical.LocationId || a.GroupLocation.Location.ModifiedDateTime != a.GroupLocationHistorical.LocationModifiedDateTime || (a.GroupLocation.Schedules.Select(s => new { ScheduleId = s.Id, s.ModifiedDateTime }).Except(a.GroupLocationHistorical.GroupLocationHistoricalSchedules.Select(hs => new { hs.ScheduleId, ModifiedDateTime = hs.ScheduleModifiedDateTime }))).Any() ); var effectiveExpireDateTime = RockDateTime.Now; int groupLocationsLoggedToHistory = 0; int groupLocationsSaveToHistoryCurrent = 0; if (groupLocationHistoricalNoLongerCurrentQuery.Any()) { var groupLocationHistoricalNoLongerCurrent = groupLocationHistoricalNoLongerCurrentQuery.Select(a => a.GroupLocationHistorical).AsNoTracking(); groupLocationsLoggedToHistory = rockContext.BulkUpdate(groupLocationHistoricalNoLongerCurrent, glh => new GroupLocationHistorical { CurrentRowIndicator = false, ExpireDateTime = effectiveExpireDateTime }); } // Insert Group Locations (that have a group with GroupType.EnableGroupHistory) that don't have a CurrentRowIndicator row yet ( or don't have a CurrentRowIndicator because it was stamped with CurrentRowIndicator=false ) var groupLocationsToAddToHistoricalCurrentsQuery = groupLocationsWithHistoryEnabledQuery.Where(gl => !groupLocationsHistoricalCurrentQuery.Any(glh => glh.GroupLocationId == gl.Id)); if (groupLocationsToAddToHistoricalCurrentsQuery.Any()) { List <GroupLocationHistorical> groupLocationHistoricalCurrentsToInsert = groupLocationsToAddToHistoricalCurrentsQuery .Include(a => a.GroupLocationTypeValue) .Include(a => a.Location).ToList() .Select(gl => GroupLocationHistorical.CreateCurrentRowFromGroupLocation(gl, effectiveExpireDateTime)).ToList(); groupLocationsSaveToHistoryCurrent = groupLocationHistoricalCurrentsToInsert.Count(); // get the current max GroupLocatiionHistorical.Id to help narrow down which ones were inserted int groupLocationHistoricalStartId = groupLocationHistoricalService.Queryable().Max(a => ( int? )a.Id) ?? 0; rockContext.BulkInsert(groupLocationHistoricalCurrentsToInsert); // since we used BulkInsert, we'll need to go back and get the Ids and the associated GroupLocation's Schedules for the GroupLocationHistorical records that we just inserted var insertedGroupLocationHistoricalIdsWithSchedules = groupLocationHistoricalService.Queryable() .Where(a => a.Id > groupLocationHistoricalStartId && a.GroupLocation.Schedules.Any()).ToList() .Select(a => new { GroupLocationHistoricalId = a.Id, a.GroupLocation.Schedules }); List <GroupLocationHistoricalSchedule> groupLocationHistoricalScheduleCurrentsToInsert = new List <GroupLocationHistoricalSchedule>(); foreach (var insertedGroupLocationHistoricalIdWithSchedules in insertedGroupLocationHistoricalIdsWithSchedules) { foreach (Schedule schedule in insertedGroupLocationHistoricalIdWithSchedules.Schedules) { groupLocationHistoricalScheduleCurrentsToInsert.Add(new GroupLocationHistoricalSchedule { GroupLocationHistoricalId = insertedGroupLocationHistoricalIdWithSchedules.GroupLocationHistoricalId, ScheduleId = schedule.Id, ScheduleName = schedule.ToString(), ScheduleModifiedDateTime = schedule.ModifiedDateTime }); } } if (groupLocationHistoricalScheduleCurrentsToInsert.Any()) { rockContext.BulkInsert(groupLocationHistoricalScheduleCurrentsToInsert); } } if (groupLocationsLoggedToHistory > 0) { _jobStatusMessages.Add($"Logged {groupLocationsLoggedToHistory} {"group location history snapshot".PluralizeIf( groupLocationsLoggedToHistory != 0 )}"); } if (groupLocationsSaveToHistoryCurrent > 0) { int newGroupLocationsAddedToHistory = groupLocationsSaveToHistoryCurrent - groupLocationsLoggedToHistory; if (newGroupLocationsAddedToHistory > 0) { _jobStatusMessages.Add($"Added {newGroupLocationsAddedToHistory} new {"group location history snapshot".PluralizeIf( newGroupLocationsAddedToHistory != 0 )}"); } } }
/// <summary> /// Updates GroupMemberHistorical for any group members in groups that have data group history enabled /// </summary> /// <param name="context">The context.</param> public void UpdateGroupMemberHistorical(IJobExecutionContext context) { var rockContext = new RockContext(); var groupMemberHistoricalService = new GroupMemberHistoricalService(rockContext); var groupMemberService = new GroupMemberService(rockContext); var groupMembersWithHistoryEnabledQuery = groupMemberService.AsNoFilter().Where(a => a.Group.GroupType.EnableGroupHistory == true).AsNoTracking(); var groupMemberHistoricalsCurrentQuery = groupMemberHistoricalService.Queryable().Where(a => a.CurrentRowIndicator == true).AsNoTracking(); // Mark GroupMemberHistorical Rows as History ( CurrentRowIndicator = false, etc ) if any of the tracked field values change var groupMemberHistoricalNoLongerCurrent = groupMemberHistoricalsCurrentQuery.Join( groupMembersWithHistoryEnabledQuery, gmh => gmh.GroupMemberId, gm => gm.Id, (gmh, gm) => new { GroupMember = gm, GroupMemberHistorical = gmh }) .Where(a => a.GroupMember.GroupRoleId != a.GroupMemberHistorical.GroupRoleId || a.GroupMember.GroupId != a.GroupMemberHistorical.GroupId || a.GroupMember.GroupRole.Name != a.GroupMemberHistorical.GroupRoleName || a.GroupMember.GroupRole.IsLeader != a.GroupMemberHistorical.IsLeader || a.GroupMember.GroupMemberStatus != a.GroupMemberHistorical.GroupMemberStatus || a.GroupMember.IsArchived != a.GroupMemberHistorical.IsArchived || a.GroupMember.ArchivedDateTime != a.GroupMemberHistorical.ArchivedDateTime || a.GroupMember.ArchivedByPersonAliasId != a.GroupMemberHistorical.ArchivedByPersonAliasId || a.GroupMember.InactiveDateTime != a.GroupMemberHistorical.InactiveDateTime ).Select(a => a.GroupMemberHistorical).AsNoTracking(); var effectiveExpireDateTime = RockDateTime.Now; int groupMembersLoggedToHistory = 0; int groupMembersSaveToHistoryCurrent = 0; if (groupMemberHistoricalNoLongerCurrent.Any()) { groupMembersLoggedToHistory = rockContext.BulkUpdate(groupMemberHistoricalNoLongerCurrent, gmh => new GroupMemberHistorical { CurrentRowIndicator = false, ExpireDateTime = effectiveExpireDateTime }); } // Insert Group Members (that have a group with GroupType.EnableGroupHistory) that don't have a CurrentRowIndicator row yet ( or don't have a CurrentRowIndicator because it was stamped with CurrentRowIndicator=false ) var groupMembersToAddToHistoricalCurrentsQuery = groupMembersWithHistoryEnabledQuery.Where(gm => !groupMemberHistoricalsCurrentQuery.Any(gmh => gmh.GroupMemberId == gm.Id)); if (groupMembersToAddToHistoricalCurrentsQuery.Any()) { List <GroupMemberHistorical> groupMemberHistoricalCurrentsToInsert = groupMembersToAddToHistoricalCurrentsQuery .Include(a => a.GroupRole) .ToList() .Select(gm => GroupMemberHistorical.CreateCurrentRowFromGroupMember(gm, effectiveExpireDateTime)).ToList(); groupMembersSaveToHistoryCurrent = groupMemberHistoricalCurrentsToInsert.Count(); rockContext.BulkInsert(groupMemberHistoricalCurrentsToInsert); } if (groupMembersLoggedToHistory > 0) { _jobStatusMessages.Add($"Logged {groupMembersLoggedToHistory} {"group member history snapshot".PluralizeIf( groupMembersLoggedToHistory != 0 )}"); } if (groupMembersSaveToHistoryCurrent > 0) { int newGroupMembersAddedToHistory = groupMembersSaveToHistoryCurrent - groupMembersLoggedToHistory; if (newGroupMembersAddedToHistory > 0) { _jobStatusMessages.Add($"Added {newGroupMembersAddedToHistory} new {"group member history snapshot".PluralizeIf( newGroupMembersAddedToHistory != 0 )}"); } } }