/// <summary> /// Get a query for people that have met prerequisites /// </summary> /// <param name="rockContext"></param> /// <param name="stepTypeView"></param> /// <returns></returns> private IQueryable <int> GetPersonIdsThatHaveMetPrerequisitesQuery(RockContext rockContext, StepTypeView stepTypeView) { var stepService = new StepService(rockContext); // We are querying for people that have met all the prerequisites for this step type // This method should not be called for stepTypes that do not have prerequisites // because that would be a query for everyone in the database var firstStepTypeId = stepTypeView.PrerequisiteStepTypeIds.First(); var prerequisiteCount = stepTypeView.PrerequisiteStepTypeIds.Count(); // Aliases that have completed the first prerequisite var query = stepService.Queryable().AsNoTracking() .Where(s => s.StepStatus.IsCompleteStatus && s.StepTypeId == firstStepTypeId) .Select(s => s.PersonAlias.PersonId); for (var i = 1; i < prerequisiteCount; i++) { var stepTypeId = stepTypeView.PrerequisiteStepTypeIds.ElementAt(i); // Aliases that have completed this subsequent prerequisite var subquery = stepService.Queryable().AsNoTracking() .Where(s => s.StepStatus.IsCompleteStatus && s.StepTypeId == stepTypeId) .Select(s => s.PersonAlias.PersonId); // Find the intersection (people in the main query who have also met this prerequisite) query = query.Intersect(subquery); } return(query); }
/// <summary> /// These are people that cannot have new step because they already /// have one and are within the minimum date range. /// </summary> /// <param name="stepTypeView">The step type view.</param> /// <param name="rockContext"></param> /// <param name="minDaysBetweenSteps"></param> /// <returns></returns> private IQueryable <int> GetPersonIdsThatCannotGetStepQuery(RockContext rockContext, StepTypeView stepTypeView, int minDaysBetweenSteps) { // We are querying for people that will ultimately be excluded from getting a new // step created from this job. var stepService = new StepService(rockContext); var query = stepService.Queryable().AsNoTracking().Where(s => s.StepTypeId == stepTypeView.StepTypeId); if (stepTypeView.AllowMultiple) { // If allow multiple and completed date is within the minDaysBetweenSteps timeframe var minStepDate = minDaysBetweenSteps >= 1 ? RockDateTime.Now.AddDays(0 - minDaysBetweenSteps) : DateTime.MinValue; query = query.Where(s => s.CompletedDateTime.HasValue && s.CompletedDateTime >= minStepDate); } else { // If not allow multiple and has a completed date at all query = query.Where(s => s.CompletedDateTime.HasValue); } return(query.Select(s => s.PersonAlias.PersonId)); }
/// <summary> /// These are people that cannot have new step because they already /// have one and are within the minimum date range. /// </summary> /// <param name="stepTypeView">The step type view.</param> /// <param name="rockContext"></param> /// <param name="minDaysBetweenSteps"></param> /// <returns></returns> private IQueryable <int> GetPeopleThatCannotGetStepQuery(RockContext rockContext, StepTypeView stepTypeView, int minDaysBetweenSteps) { var stepService = new StepService(rockContext); var minStepDate = DateTime.MinValue; // We are querying for people that will ultimately be excluded from getting a new // step created from this job. // If duplicates are not allowed, then we want to find anyone with a step ever // If duplicates are allowed and a day range is set, then it is within that timeframe. if (stepTypeView.AllowMultiple && minDaysBetweenSteps >= 1) { minStepDate = RockDateTime.Now.AddDays(0 - minDaysBetweenSteps); } var query = stepService.Queryable().AsNoTracking() .Where(s => s.StepTypeId == stepTypeView.StepTypeId && (!s.CompletedDateTime.HasValue || s.CompletedDateTime.Value >= minStepDate)) .Select(s => s.PersonAlias.PersonId); return(query); }
/// <summary> /// Processes the step type. Add steps for everyone in the dataview /// </summary> /// <param name="jobContext">The job context.</param> /// <param name="stepTypeView">The step type view.</param> /// <param name="minDaysBetweenSteps">The minimum days between steps.</param> /// <param name="addedResults">The added results.</param> /// <param name="updatedResults">The updated results.</param> /// <param name="errorMessages">The error message.</param> private void ProcessStepType( IJobExecutionContext jobContext, StepTypeView stepTypeView, int minDaysBetweenSteps, ConcurrentBag <int> addedResults, ConcurrentBag <int> updatedResults, out List <string> errorMessages) { errorMessages = new List <string>(); var rockContextGetList = new RockContext(); rockContextGetList.Database.CommandTimeout = SqlCommandTimeoutSeconds; // Steps are created with a status of "complete", so if we need to know the status id var stepStatusId = stepTypeView.CompletedStepStatusIds.FirstOrDefault(); if (stepStatusId == default) { errorMessages.Add($"The Step Type with id {stepTypeView.StepTypeId} does not have a valid Complete Status to use"); return; } // Get the dataview configured for the step type var dataViewService = new DataViewService(rockContextGetList); var dataview = dataViewService.Get(stepTypeView.AutoCompleteDataViewId); if (dataview == null) { errorMessages.Add($"The dataview {stepTypeView.AutoCompleteDataViewId} for step type {stepTypeView.StepTypeId} did not resolve"); return; } // We can use the dataview to get the person alias id query var dataViewGetQueryArgs = new DataViewGetQueryArgs { DbContext = rockContextGetList, DatabaseTimeoutSeconds = SqlCommandTimeoutSeconds }; IQueryable <IEntity> dataviewQuery; try { dataviewQuery = dataview.GetQuery(dataViewGetQueryArgs); } catch (Exception ex) { errorMessages.Add(ex.Message); ExceptionLogService.LogException(ex); return; } if (dataviewQuery == null) { errorMessages.Add($"Generating a query for dataview {stepTypeView.AutoCompleteDataViewId} for step type {stepTypeView.StepTypeId} was not successful"); return; } // This query contains person ids in the dataview var personIdQuery = dataviewQuery.AsNoTracking().Select(e => e.Id); // Get the query for people that cannot get a new step var personIdsThatCannotGetStepQuery = GetPersonIdsThatCannotGetStepQuery(rockContextGetList, stepTypeView, minDaysBetweenSteps); // Subtract the people that cannot get a new step personIdQuery = personIdQuery.Except(personIdsThatCannotGetStepQuery); // If there are prerequisites, then subtract the people that cannot get the step because of unmet prerequisites if (stepTypeView.PrerequisiteStepTypeIds.Any()) { var personIdsThatHaveMetPrerequisitesQuery = GetPersonIdsThatHaveMetPrerequisitesQuery(rockContextGetList, stepTypeView); personIdQuery = personIdQuery.Intersect(personIdsThatHaveMetPrerequisitesQuery); } // Convert to person aliases ids var personAliasService = new PersonAliasService(rockContextGetList); var personInfoList = personAliasService.GetPrimaryAliasQuery() .Where(a => personIdQuery.Contains(a.PersonId)) .Select(a => new { PersonId = a.PersonId, PrimaryAliasId = a.Id }) .ToList(); // Add or update steps for each of the remaining aliases that have met all the conditions var stepServiceGetList = new StepService(rockContextGetList); var now = RockDateTime.Now; var addedCount = 0; var updatedCount = 0; // Query for existing incomplete steps for the people var existingIncompleteStepIdsByPersonId = stepServiceGetList.Queryable() .Where(s => s.StepTypeId == stepTypeView.StepTypeId && personIdQuery.Contains(s.PersonAlias.PersonId) && !s.CompletedDateTime.HasValue) .Select(a => new { a.PersonAlias.PersonId, StepId = a.Id }) .ToList() .GroupBy(a => a.PersonId) .ToDictionary( k => k.Key, // just in case the Person is has more than one incomplete step for this step type, just use the latest one // it should clean it self up on subsequent runs since the other steps for this person wouldn't have been marked complete yet v => v.Max(s => s.StepId) ); long totalCount = personInfoList.Count; long progressCount = 0; foreach (var personIdInfo in personInfoList) { var personId = personIdInfo.PersonId; var personPrimaryAliasId = personIdInfo.PrimaryAliasId; var existingStepId = existingIncompleteStepIdsByPersonId.GetValueOrNull(personId); using (var rockContextLoop = new RockContext()) { var stepServiceLoop = new StepService(rockContextLoop); Step step; if (existingStepId.HasValue) { step = stepServiceLoop.Get(existingStepId.Value); } else { step = new Step { StepTypeId = stepTypeView.StepTypeId, Caption = stepTypeView.Name, StartDateTime = now, PersonAliasId = personPrimaryAliasId }; } step.CompletedDateTime = now; step.StepStatusId = stepStatusId; if (!existingStepId.HasValue) { stepServiceLoop.AddWithoutValidation(step); addedCount++; } else { updatedCount++; } rockContextLoop.SaveChanges(); } progressCount++; // Update the progress every 5 seconds if ((RockDateTime.Now - _lastProgressUpdate).TotalSeconds >= 5) { try { jobContext.UpdateLastStatusMessage($"Processing {stepTypeView.Name } steps : {progressCount}/{totalCount}"); } catch (Exception ex) { // ignore, but write to debug output System.Diagnostics.Debug.WriteLine($"Error updating LastStatusMessage for ProcessStepType loop: {ex}"); } finally { _lastProgressUpdate = RockDateTime.Now; } } } addedResults.Add(addedCount); updatedResults.Add(updatedCount); }
/// <summary> /// Processes the step type. Add steps for everyone in the dataview /// </summary> /// <param name="stepTypeView">The step type view.</param> /// <param name="errorMessages">The error message.</param> /// <param name="minDaysBetweenSteps"></param> /// <returns></returns> private int ProcessStepType(StepTypeView stepTypeView, int minDaysBetweenSteps, out List <string> errorMessages) { errorMessages = new List <string>(); var rockContext = new RockContext(); // Steps are created with a status of "complete", so if we need to know the status id var stepStatusId = stepTypeView.CompletedStepStatusIds.FirstOrDefault(); if (stepStatusId == default) { errorMessages.Add($"The Step Type with id {stepTypeView.StepTypeId} does not have a valid Complete Status to use"); return(0); } // Get the dataview configured for the step type var dataViewService = new DataViewService(rockContext); var dataview = dataViewService.Get(stepTypeView.AutoCompleteDataViewId); if (dataview == null) { errorMessages.Add($"The dataview {stepTypeView.AutoCompleteDataViewId} for step type {stepTypeView.StepTypeId} did not resolve"); return(0); } // We can use the dataview to get the person alias id query var dataviewQuery = dataview.GetQuery(null, rockContext, null, out var dataviewQueryErrors); if (dataviewQueryErrors != null && dataviewQueryErrors.Any()) { errorMessages.AddRange(dataviewQueryErrors); return(0); } if (dataviewQuery == null) { errorMessages.Add($"Generating a query for dataview {stepTypeView.AutoCompleteDataViewId} for step type {stepTypeView.StepTypeId} was not successful"); return(0); } // This query contains person ids in the dataview var personIdQuery = dataviewQuery.AsNoTracking().Select(e => e.Id); // Get the query for person aliases that cannot get a new step var peopleThatCannotGetStepQuery = GetPeopleThatCannotGetStepQuery(rockContext, stepTypeView, minDaysBetweenSteps); // Subtract the people that cannot get a new step personIdQuery = personIdQuery.Except(peopleThatCannotGetStepQuery); // If there are prerequisites, then subtract the people that cannot be the step because of unmet prerequisites if (stepTypeView.PrerequisiteStepTypeIds.Any()) { var peopleThatHaveMetPrerequisitesQuery = GetPeopleThatHaveMetPrerequisitesQuery(rockContext, stepTypeView); personIdQuery = personIdQuery.Intersect(peopleThatHaveMetPrerequisitesQuery); } // Convert to person aliases ids var personAliasService = new PersonAliasService(rockContext); var personAliasIds = personAliasService.Queryable().AsNoTracking() .Where(pa => personIdQuery.Contains(pa.PersonId)) .Select(pa => pa.Id) .Distinct() .ToList(); // Add steps for each of the remaining aliases that have met all the conditions var stepService = new StepService(rockContext); var now = RockDateTime.Now; var count = 0; foreach (var personAliasId in personAliasIds) { var step = new Step { StepTypeId = stepTypeView.StepTypeId, CompletedDateTime = now, StartDateTime = now, StepStatusId = stepStatusId, PersonAliasId = personAliasId }; stepService.AddWithoutValidation(step); count++; if (count % 100 == 0) { rockContext.SaveChanges(); } } rockContext.SaveChanges(); return(count); }