/// <summary> /// Start an async task to calculate steak data and then copy it to the enrollment model /// </summary> /// <param name="streakId">The streak identifier.</param> public static void RefreshStreakDenormalizedProperties(int streakId) { var rockContext = new RockContext(); var streakService = new StreakService(rockContext); var streakTypeService = new StreakTypeService(rockContext); // Get the streak data and validate it var streakData = streakTypeService.GetStreakData(streakId, out var errorMessage); if (!errorMessage.IsNullOrWhiteSpace()) { ExceptionLogService.LogException(errorMessage); return; } if (streakData == null) { ExceptionLogService.LogException("Streak Data was null, but no error was specified"); return; } // Get the streak and apply updated information to it var streak = streakService.Get(streakId); if (streak == null) { ExceptionLogService.LogException($"The streak with id {streakId} was not found (it may have been deleted)"); return; } CopyStreakDataToStreakModel(streakData, streak); rockContext.SaveChanges(true); }
/// <summary> /// Called after the save operation has been executed /// </summary> /// <remarks> /// This method is only called if <see cref="M:Rock.Data.EntitySaveHook`1.PreSave" /> returns /// without error. /// </remarks> protected override void PostSave() { if (this.PreSaveState != EntityContextState.Deleted) { // The data context save operation doesn't need to wait for this to complete Task.Run(() => StreakTypeService.HandleInteractionRecord(Entity.Id)); } base.PostSave(); }
/// <summary> /// Method that will be called on an entity immediately after the item is saved by context /// </summary> /// <param name="dbContext">The database context.</param> public override void PostSaveChanges(Data.DbContext dbContext) { if (_declinedScheduledAttendance) { new GroupScheduleCancellationTransaction(this).Enqueue(); } if (!_isDeleted) { StreakTypeService.HandleAttendanceRecord(this); } base.PostSaveChanges(dbContext); }
/// <summary> /// Method that will be called on an entity immediately after the item is saved by context /// </summary> /// <remarks> /// This method is only called if <see cref="M:Rock.Data.EntitySaveHook`1.PreSave" /> returns /// without error. /// </remarks> protected override void PostSave() { if (_declinedScheduledAttendance) { new LaunchGroupScheduleCancellationWorkflow.Message() { AttendanceId = Entity.Id }.Send(); } if (!_isDeleted) { // Process any streaks that may occur as a result of adding/modifying an attendance record. // If there are any, they need to be processed in this thread in case there are any achievement changes // that need to be detected as a result of this attendance. StreakTypeService.HandleAttendanceRecord(Entity.Id); } var rockContext = ( RockContext )this.RockContext; if (PersonAttendanceHistoryChangeList?.Any() == true) { var attendanceId = Entity.Id; if (preSavePersonAliasId.HasValue) { var attendeePersonId = new PersonAliasService(this.RockContext).GetPersonId(preSavePersonAliasId.Value); if (attendeePersonId.HasValue) { var entityTypeType = typeof(Person); var relatedEntityTypeType = typeof(Attendance); HistoryService.SaveChanges( rockContext, entityTypeType, Rock.SystemGuid.Category.HISTORY_ATTENDANCE_CHANGES.AsGuid(), attendeePersonId.Value, this.PersonAttendanceHistoryChangeList, $"Attendance {attendanceId}", relatedEntityTypeType, attendanceId, true, Entity.ModifiedByPersonAliasId, rockContext.SourceOfChange); } } } base.PostSave(); }
/// <summary> /// Method that will be called on an entity immediately after the item is saved by context /// </summary> /// <param name="dbContext">The database context.</param> public override void PostSaveChanges(Data.DbContext dbContext) { if (_declinedScheduledAttendance) { new GroupScheduleCancellationTransaction(this).Enqueue(); } if (!_isDeleted) { // The data context save operation doesn't need to wait for this to complete Task.Run(() => StreakTypeService.HandleAttendanceRecord(this)); } base.PostSaveChanges(dbContext); }
/// <summary> /// BulkInserts Interaction Records /// </summary> /// <remarks> /// If any PersonAliasId references a PersonAliasId record that doesn't exist, the field value will be set to null. /// Also, if the InteractionComponent Id (or Guid) is specified, but references a Interaction Component record that doesn't exist /// the Interaction will not be recorded. /// </remarks> /// <param name="interactionsImport">The interactions import.</param> internal static void BulkInteractionImport(InteractionsImport interactionsImport) { if (interactionsImport == null) { throw new Exception("InteractionsImport must be assigned a value."); } var interactionImportList = interactionsImport.Interactions; if (interactionImportList == null || !interactionImportList.Any()) { // if there aren't any return return; } /* 2020-05-14 MDP * Make sure that all the PersonAliasIds in the import exist in the database. * For performance reasons, look them up all at one and keep a list of valid ones. * * If there are any PersonAliasIds that aren't valid, * we decided that just set the PersonAliasId to null (we want ignore bad data). */ HashSet <int> validPersonAliasIds = interactionsImport.GetValidPersonAliasIds(); List <Interaction> interactionsToInsert = new List <Interaction>(); foreach (InteractionImport interactionImport in interactionImportList) { if (interactionImport.Interaction == null) { throw new ArgumentNullException("InteractionImport.Interaction can not be null"); } // Determine which Channel this should be set to if (interactionImport.InteractionChannelId.HasValue) { // make sure it is a valid Id interactionImport.InteractionChannelId = InteractionChannelCache.Get(interactionImport.InteractionChannelId.Value)?.Id; } // Determine which Channel Type Medium this should be set to if (interactionImport.InteractionChannelChannelTypeMediumValueId.HasValue) { // make sure it is a valid Id interactionImport.InteractionChannelChannelTypeMediumValueId = DefinedValueCache.Get(interactionImport.InteractionChannelChannelTypeMediumValueId.Value)?.Id; } if (!interactionImport.InteractionChannelChannelTypeMediumValueId.HasValue) { if (interactionImport.InteractionChannelChannelTypeMediumValueGuid.HasValue) { interactionImport.InteractionChannelChannelTypeMediumValueId = DefinedValueCache.GetId(interactionImport.InteractionChannelChannelTypeMediumValueGuid.Value); } } if (!interactionImport.InteractionChannelId.HasValue) { if (interactionImport.InteractionChannelGuid.HasValue) { interactionImport.InteractionChannelId = InteractionChannelCache.GetId(interactionImport.InteractionChannelGuid.Value); } // if InteractionChannelId is still null, lookup (or create) an InteractionChannel from InteractionChannelForeignKey (if it is specified) if (interactionImport.InteractionChannelId == null && interactionImport.InteractionChannelForeignKey.IsNotNullOrWhiteSpace()) { interactionImport.InteractionChannelId = InteractionChannelCache.GetCreateChannelIdByForeignKey(interactionImport.InteractionChannelForeignKey, interactionImport.InteractionChannelName, interactionImport.InteractionChannelChannelTypeMediumValueId); } else { /* 2020-05-14 MDP * Discussed this and decided that if we tried InteractionChannelId and InteractionChannelGuid, and InteractionChannelForeignKey was not specified, * we'll just skip over this record */ continue; } } // Determine which Component this should be set to if (interactionImport.InteractionComponentId.HasValue) { // make sure it is a valid Id interactionImport.InteractionComponentId = InteractionComponentCache.Get(interactionImport.InteractionComponentId.Value)?.Id; } if (!interactionImport.InteractionComponentId.HasValue) { if (interactionImport.InteractionComponentGuid.HasValue) { interactionImport.InteractionComponentId = InteractionComponentCache.GetId(interactionImport.InteractionComponentGuid.Value); } // if InteractionComponentId is still null, lookup (or create) an InteractionComponent from the ForeignKey and ChannelId if (interactionImport.InteractionComponentForeignKey.IsNotNullOrWhiteSpace()) { interactionImport.InteractionComponentId = InteractionComponentCache.GetComponentIdByForeignKeyAndChannelId( interactionImport.InteractionComponentForeignKey, interactionImport.InteractionChannelId.Value, interactionImport.InteractionComponentName); } else { /* 2020-05-14 MDP * Discussed this and decided that and if we tried InteractionComponentId and InteractionComponentGuid, and InteractionComponentForeignKey was not specified, * we'll just skip over this record */ continue; } } } foreach (InteractionImport interactionImport in interactionImportList.Where(a => a.InteractionComponentId.HasValue)) { Interaction interaction = new Interaction { InteractionComponentId = interactionImport.InteractionComponentId.Value }; interaction.InteractionDateTime = interactionImport.Interaction.InteractionDateTime; // if operation is over 25, truncate it interaction.Operation = interactionImport.Interaction.Operation.Truncate(25); interaction.InteractionComponentId = interactionImport.InteractionComponentId.Value; interaction.EntityId = interactionImport.Interaction.EntityId; if (interactionImport.Interaction.RelatedEntityTypeId.HasValue) { /* 2020-05-14 MDP * We want to ignore bad data, so first see if the RelatedEntityTypeId exists by looking it up in a cache. * If it doesn't exist, it'll set RelatedEntityTypeId to null (so that we don't get a database constraint error) */ interaction.RelatedEntityTypeId = EntityTypeCache.Get(interactionImport.Interaction.RelatedEntityTypeId.Value)?.Id; } interaction.RelatedEntityId = interactionImport.Interaction.RelatedEntityId; if (interactionImport.Interaction.PersonAliasId.HasValue) { /* 2020-05-14 MDP * We want to ignore bad data, so see if the specified PersonAliasId exists in the validPersonAliasIds that we lookup up * If it doesn't exist, we'll leave interaction.PersonAliasId null (so that we don't get a database constraint error) */ if (validPersonAliasIds.Contains(interactionImport.Interaction.PersonAliasId.Value)) { interaction.PersonAliasId = interactionImport.Interaction.PersonAliasId.Value; } } // BulkImport doesn't include Session information TODO??? interaction.InteractionSessionId = null; // if the summary is over 500 chars, truncate with addEllipsis=true interaction.InteractionSummary = interactionImport.Interaction.InteractionSummary.Truncate(500, true); interaction.InteractionData = interactionImport.Interaction.InteractionData; interaction.PersonalDeviceId = interactionImport.Interaction.PersonalDeviceId; interaction.InteractionEndDateTime = interactionImport.Interaction.InteractionEndDateTime; // Campaign related fields, we'll truncate those if they are too long interaction.Source = interactionImport.Interaction.Source.Truncate(25); interaction.Medium = interactionImport.Interaction.Medium.Truncate(25); interaction.Campaign = interactionImport.Interaction.Campaign.Truncate(50); interaction.Content = interactionImport.Interaction.Content.Truncate(50); interaction.Term = interactionImport.Interaction.Term.Truncate(50); interaction.ForeignId = interactionImport.Interaction.ForeignId; interaction.ForeignKey = interactionImport.Interaction.ForeignKey; interaction.ForeignGuid = interactionImport.Interaction.ForeignGuid; interaction.ChannelCustom1 = interactionImport.Interaction.ChannelCustom1.Truncate(500, true); interaction.ChannelCustom2 = interactionImport.Interaction.ChannelCustom2.Truncate(2000, true); interaction.ChannelCustomIndexed1 = interactionImport.Interaction.ChannelCustomIndexed1.Truncate(500, true); interaction.InteractionLength = interactionImport.Interaction.InteractionLength; interaction.InteractionTimeToServe = interactionImport.Interaction.InteractionTimeToServe; interactionsToInsert.Add(interaction); } using (var rockContext = new RockContext()) { rockContext.BulkInsert(interactionsToInsert); } // This logic is normally handled in the Interaction.PostSave method, but since the BulkInsert bypasses those // model hooks, streaks need to be updated here. Also, it is not necessary for this logic to complete before this // transaction can continue processing and exit, so update the streak using a task. // Only launch this task if there are StreakTypes configured that have interactions. Otherwise several // database calls are made only to find out there are no streak types defined. if (StreakTypeCache.All().Any(s => s.IsInteractionRelated)) { // Ids do not exit for the interactions in the collection since they were bulk imported. // Read their ids from their guids and append the id. var insertedGuids = interactionsToInsert.Select(i => i.Guid).ToList(); var interactionIds = new InteractionService(new RockContext()).Queryable() .Where(i => insertedGuids.Contains(i.Guid)) .Select(i => new { i.Id, i.Guid }) .ToList(); foreach (var interactionId in interactionIds) { var interaction = interactionsToInsert.Where(i => i.Guid == interactionId.Guid).FirstOrDefault(); if (interaction != null) { interaction.Id = interactionId.Id; } } // Launch task interactionsToInsert.ForEach(i => Task.Run(() => StreakTypeService.HandleInteractionRecord(i.Id))); } }