Esempio n. 1
0
        /// <summary>
        /// Processes the follower PersonId for the SuggestionType
        /// </summary>
        /// <param name="followerPersonId">The follower person identifier.</param>
        /// <param name="suggestionTypeComponent">The suggestion type component.</param>
        /// <param name="primaryAliasIds">The primary alias ids.</param>
        /// <param name="entityIds">The entity ids.</param>
        /// <param name="entityTypeId">The entity type identifier.</param>
        /// <param name="timestamp">The timestamp.</param>
        private void ProcessFollowerPersonId(int followerPersonId, SuggestionTypeComponent suggestionTypeComponent, Dictionary <int, int> primaryAliasIds, List <int> entityIds, int entityTypeId, DateTime timestamp)
        {
            using (var suggestionContext = new RockContext())
            {
                var followingSuggestedService = new FollowingSuggestedService(suggestionContext);

                // Read all the existing suggestions for this type and the returned followers
                var existingSuggestions = followingSuggestedService
                                          .Queryable("PersonAlias")
                                          .Where(s => s.SuggestionTypeId == suggestionTypeComponent.FollowingSuggestionType.Id &&
                                                 s.PersonAlias.PersonId == followerPersonId)
                                          .ToList();

                // Look  through the returned suggestions
                foreach (var followedEntityId in suggestionTypeComponent.PersonEntitySuggestions.Where(s => s.PersonId == followerPersonId).Select(s => s.EntityId))
                {
                    // Make sure person isn't already following this entity
                    if (!suggestionTypeComponent.ExistingFollowings.ContainsKey(followerPersonId) ||
                        !suggestionTypeComponent.ExistingFollowings[followerPersonId].Contains(followedEntityId))
                    {
                        // If this person had a primary alias id
                        if (primaryAliasIds.ContainsKey(followerPersonId))
                        {
                            entityIds.Add(followedEntityId);

                            // Look for existing suggestion for this person and entity
                            var suggestion = existingSuggestions
                                             .Where(s => s.EntityId == followedEntityId)
                                             .OrderByDescending(s => s.StatusChangedDateTime)
                                             .FirstOrDefault();

                            // If not found add it if needed
                            if (suggestion == null)
                            {
                                bool addSuggestion;
                                ProcessFollowingSuggestionAndPersonAliasEntity(followerPersonId, suggestionTypeComponent, entityIds, entityTypeId, suggestionContext, followedEntityId, out addSuggestion);

                                if (addSuggestion)
                                {
                                    // This is a new entity ID so insert it
                                    suggestion = new FollowingSuggested
                                    {
                                        EntityTypeId          = entityTypeId,
                                        EntityId              = followedEntityId,
                                        PersonAliasId         = primaryAliasIds[followerPersonId],
                                        SuggestionTypeId      = suggestionTypeComponent.FollowingSuggestionType.Id,
                                        Status                = FollowingSuggestedStatus.PendingNotification,
                                        StatusChangedDateTime = timestamp
                                    };

                                    followingSuggestedService.Add(suggestion);
                                }
                            }
                            else
                            {
                                ProcessFollowingSuggestionAndPersonAliasEntity(followerPersonId, suggestionTypeComponent, entityIds, entityTypeId, suggestionContext, followedEntityId);

                                // If found, and it has not been ignored, and it's time to promote again, update the promote date
                                if (suggestion.Status != FollowingSuggestedStatus.Ignored &&
                                    suggestionTypeComponent.FollowingSuggestionType.ReminderDays.HasValue &&
                                    (
                                        !suggestion.LastPromotedDateTime.HasValue ||
                                        suggestion.LastPromotedDateTime.Value.AddDays(suggestionTypeComponent.FollowingSuggestionType.ReminderDays.Value) <= timestamp
                                    ))
                                {
                                    if (suggestion.Status != FollowingSuggestedStatus.PendingNotification)
                                    {
                                        suggestion.StatusChangedDateTime = timestamp;
                                        suggestion.Status = FollowingSuggestedStatus.PendingNotification;
                                    }
                                }
                            }
                        }
                    }
                }

                // Save the suggestions for this type
                suggestionContext.SaveChanges();
            }
        }
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Execute(IJobExecutionContext context)
        {
            var exceptionMsgs = new List <string>();

            JobDataMap dataMap         = context.JobDetail.JobDataMap;
            Guid?      groupGuid       = dataMap.GetString("EligibleFollowers").AsGuidOrNull();
            Guid?      systemEmailGuid = dataMap.GetString("EmailTemplate").AsGuidOrNull();
            int        followingSuggestionsEmailsSent       = 0;
            int        followingSuggestionsSuggestionsTotal = 0;

            if (groupGuid.HasValue && systemEmailGuid.HasValue)
            {
                using (var rockContext = new RockContext())
                {
                    var followingService = new FollowingService(rockContext);

                    // The people who are eligible to get following suggestions based on the group type setting for this job
                    var eligiblePersonIds = new GroupMemberService(rockContext)
                                            .Queryable().AsNoTracking()
                                            .Where(m =>
                                                   m.Group != null &&
                                                   m.Group.Guid.Equals(groupGuid.Value) &&
                                                   m.GroupMemberStatus == GroupMemberStatus.Active &&
                                                   m.Person != null &&
                                                   m.Person.Email != null &&
                                                   m.Person.Email != "")
                                            .Select(m => m.PersonId)
                                            .Distinct();

                    // check to see if there are any event types that require notification
                    var followerPersonIds = new List <int>();
                    if (new FollowingEventTypeService(rockContext)
                        .Queryable().AsNoTracking()
                        .Any(e => e.IsNoticeRequired))
                    {
                        // if so, include all eligible people
                        followerPersonIds = eligiblePersonIds.ToList();
                    }
                    else
                    {
                        // if not, filter the list of eligible people down to only those that actually have subscribed to one or more following events
                        followerPersonIds = new FollowingEventSubscriptionService(rockContext)
                                            .Queryable().AsNoTracking()
                                            .Where(f => eligiblePersonIds.Contains(f.PersonAlias.PersonId))
                                            .Select(f => f.PersonAlias.PersonId)
                                            .Distinct()
                                            .ToList();
                    }

                    if (followerPersonIds.Any())
                    {
                        // Get the primary person alias id for each of the followers
                        var primaryAliasIds = new Dictionary <int, int>();
                        new PersonAliasService(rockContext)
                        .Queryable().AsNoTracking()
                        .Where(a =>
                               followerPersonIds.Contains(a.PersonId) &&
                               a.PersonId == a.AliasPersonId)
                        .ToList()
                        .ForEach(a => primaryAliasIds.AddOrIgnore(a.PersonId, a.Id));

                        // Get current date/time.
                        var timestamp = RockDateTime.Now;

                        var suggestionTypes = new FollowingSuggestionTypeService(rockContext)
                                              .Queryable().AsNoTracking()
                                              .Where(s => s.IsActive)
                                              .OrderBy(s => s.Name)
                                              .ToList();

                        var components        = new Dictionary <int, SuggestionComponent>();
                        var suggestedEntities = new Dictionary <int, Dictionary <int, IEntity> >();

                        foreach (var suggestionType in suggestionTypes)
                        {
                            try
                            {
                                // Get the suggestion type component
                                var suggestionComponent = suggestionType.GetSuggestionComponent();
                                if (suggestionComponent != null)
                                {
                                    components.Add(suggestionType.Id, suggestionComponent);

                                    // Get the entitytype for this suggestion type
                                    var suggestionEntityType = EntityTypeCache.Read(suggestionComponent.FollowedType);
                                    if (suggestionEntityType != null)
                                    {
                                        var entityIds = new List <int>();

                                        // Call the components method to return all of it's suggestions
                                        var personEntitySuggestions = suggestionComponent.GetSuggestions(suggestionType, followerPersonIds);

                                        // If any suggestions were returned by the component
                                        if (personEntitySuggestions.Any())
                                        {
                                            int    entityTypeId = suggestionEntityType.Id;
                                            string reasonNote   = suggestionType.ReasonNote;

                                            // Get the existing followings for any of the followers
                                            var existingFollowings = new Dictionary <int, List <int> >();
                                            foreach (var following in followingService.Queryable("PersonAlias").AsNoTracking()
                                                     .Where(f =>
                                                            f.EntityTypeId == entityTypeId &&
                                                            followerPersonIds.Contains(f.PersonAlias.PersonId)))
                                            {
                                                existingFollowings.AddOrIgnore(following.PersonAlias.PersonId, new List <int>());
                                                existingFollowings[following.PersonAlias.PersonId].Add(following.EntityId);
                                            }

                                            // Loop through each follower
                                            foreach (var followerPersonId in personEntitySuggestions
                                                     .Select(s => s.PersonId)
                                                     .Distinct())
                                            {
                                                using (var suggestionContext = new RockContext())
                                                {
                                                    var followingSuggestedService = new FollowingSuggestedService(suggestionContext);

                                                    // Read all the existing suggestions for this type and the returned followers
                                                    var existingSuggestions = followingSuggestedService
                                                                              .Queryable("PersonAlias")
                                                                              .Where(s =>
                                                                                     s.SuggestionTypeId == suggestionType.Id &&
                                                                                     s.PersonAlias.PersonId == followerPersonId)
                                                                              .ToList();

                                                    // Look  through the returned suggestions
                                                    foreach (var followedEntityId in personEntitySuggestions
                                                             .Where(s => s.PersonId == followerPersonId)
                                                             .Select(s => s.EntityId))
                                                    {
                                                        // Make sure person isn't already following this entity
                                                        if (!existingFollowings.ContainsKey(followerPersonId) ||
                                                            !existingFollowings[followerPersonId].Contains(followedEntityId))
                                                        {
                                                            // If this person had a primary alias id
                                                            if (primaryAliasIds.ContainsKey(followerPersonId))
                                                            {
                                                                entityIds.Add(followedEntityId);

                                                                // Look for existing suggestion for this person and entity
                                                                var suggestion = existingSuggestions
                                                                                 .Where(s => s.EntityId == followedEntityId)
                                                                                 .OrderByDescending(s => s.StatusChangedDateTime)
                                                                                 .FirstOrDefault();

                                                                // If not found, add one
                                                                if (suggestion == null)
                                                                {
                                                                    suggestion = new FollowingSuggested();
                                                                    suggestion.EntityTypeId          = entityTypeId;
                                                                    suggestion.EntityId              = followedEntityId;
                                                                    suggestion.PersonAliasId         = primaryAliasIds[followerPersonId];
                                                                    suggestion.SuggestionTypeId      = suggestionType.Id;
                                                                    suggestion.Status                = FollowingSuggestedStatus.PendingNotification;
                                                                    suggestion.StatusChangedDateTime = timestamp;
                                                                    followingSuggestedService.Add(suggestion);
                                                                }
                                                                else
                                                                {
                                                                    // If found, and it has not been ignored, and it's time to promote again, update the promote date
                                                                    if (suggestion.Status != FollowingSuggestedStatus.Ignored &&
                                                                        (
                                                                            !suggestionType.ReminderDays.HasValue ||
                                                                            !suggestion.LastPromotedDateTime.HasValue ||
                                                                            suggestion.LastPromotedDateTime.Value.AddDays(suggestionType.ReminderDays.Value) <= timestamp
                                                                        ))
                                                                    {
                                                                        if (suggestion.Status != FollowingSuggestedStatus.PendingNotification)
                                                                        {
                                                                            suggestion.StatusChangedDateTime = timestamp;
                                                                            suggestion.Status = FollowingSuggestedStatus.PendingNotification;
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }

                                                    // Save the suggestions for this type
                                                    suggestionContext.SaveChanges();
                                                }
                                            }
                                        }

                                        // If any entities are being suggested for this type, query database for them and save to dictionary
                                        if (entityIds.Any())
                                        {
                                            if (suggestionEntityType.AssemblyName != null)
                                            {
                                                // get the actual type of what is being followed
                                                Type entityType = suggestionEntityType.GetEntityType();
                                                if (entityType != null)
                                                {
                                                    // Get generic queryable method and query all the entities that are being followed
                                                    Type[]             modelType          = { entityType };
                                                    Type               genericServiceType = typeof(Rock.Data.Service <>);
                                                    Type               modelServiceType   = genericServiceType.MakeGenericType(modelType);
                                                    Rock.Data.IService serviceInstance    = Activator.CreateInstance(modelServiceType, new object[] { rockContext }) as IService;
                                                    MethodInfo         qryMethod          = serviceInstance.GetType().GetMethod("Queryable", new Type[] { });
                                                    var entityQry  = qryMethod.Invoke(serviceInstance, new object[] { }) as IQueryable <IEntity>;
                                                    var entityList = entityQry.AsNoTracking().Where(q => entityIds.Contains(q.Id)).ToList();
                                                    if (entityList != null && entityList.Any())
                                                    {
                                                        var entities = new Dictionary <int, IEntity>();
                                                        entityList.ForEach(e => entities.Add(e.Id, e));
                                                        suggestedEntities.Add(suggestionType.Id, entities);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                exceptionMsgs.Add(string.Format("An exception occurred calculating suggestions for the '{0}' suggestion type:{1}    {2}", suggestionType.Name, Environment.NewLine, ex.Messages().AsDelimited(Environment.NewLine + "   ")));
                                ExceptionLogService.LogException(ex, System.Web.HttpContext.Current);
                            }
                        }

                        var allSuggestions = new FollowingSuggestedService(rockContext)
                                             .Queryable("PersonAlias")
                                             .Where(s => s.Status == FollowingSuggestedStatus.PendingNotification)
                                             .ToList();

                        var suggestionPersonIds = allSuggestions
                                                  .Where(s => followerPersonIds.Contains(s.PersonAlias.PersonId))
                                                  .Select(s => s.PersonAlias.PersonId)
                                                  .Distinct()
                                                  .ToList();

                        var appRoot = Rock.Web.Cache.GlobalAttributesCache.Read(rockContext).GetValue("ExternalApplicationRoot");

                        foreach (var person in new PersonService(rockContext)
                                 .Queryable().AsNoTracking()
                                 .Where(p => suggestionPersonIds.Contains(p.Id))
                                 .ToList())
                        {
                            try
                            {
                                var personSuggestionNotices = new List <FollowingSuggestionNotices>();

                                foreach (var suggestionType in suggestionTypes)
                                {
                                    var component = components.ContainsKey(suggestionType.Id) ? components[suggestionType.Id] : null;
                                    if (component != null && suggestedEntities.ContainsKey(suggestionType.Id))
                                    {
                                        var entities = new List <IEntity>();
                                        foreach (var suggestion in allSuggestions
                                                 .Where(s =>
                                                        s.PersonAlias.PersonId == person.Id &&
                                                        s.SuggestionTypeId == suggestionType.Id)
                                                 .ToList())
                                        {
                                            if (suggestedEntities[suggestionType.Id].ContainsKey(suggestion.EntityId))
                                            {
                                                entities.Add(suggestedEntities[suggestionType.Id][suggestion.EntityId]);
                                                suggestion.LastPromotedDateTime = timestamp;
                                                suggestion.Status = FollowingSuggestedStatus.Suggested;
                                            }
                                        }

                                        var notices = new List <string>();
                                        foreach (var entity in component.SortEntities(entities))
                                        {
                                            notices.Add(component.FormatEntityNotification(suggestionType, entity));
                                        }

                                        if (notices.Any())
                                        {
                                            personSuggestionNotices.Add(new FollowingSuggestionNotices(suggestionType, notices));
                                        }
                                    }
                                }

                                if (personSuggestionNotices.Any())
                                {
                                    // Send the notice
                                    var recipients  = new List <RecipientData>();
                                    var mergeFields = new Dictionary <string, object>();
                                    mergeFields.Add("Person", person);
                                    mergeFields.Add("Suggestions", personSuggestionNotices.OrderBy(s => s.SuggestionType.Order).ToList());
                                    recipients.Add(new RecipientData(person.Email, mergeFields));
                                    Email.Send(systemEmailGuid.Value, recipients, appRoot);
                                    followingSuggestionsEmailsSent       += recipients.Count();
                                    followingSuggestionsSuggestionsTotal += personSuggestionNotices.Count();
                                }

                                rockContext.SaveChanges();
                            }
                            catch (Exception ex)
                            {
                                exceptionMsgs.Add(string.Format("An exception occurred sending suggestions to '{0}':{1}    {2}", person.FullName, Environment.NewLine, ex.Messages().AsDelimited(Environment.NewLine + "   ")));
                                ExceptionLogService.LogException(ex, System.Web.HttpContext.Current);
                            }
                        }
                    }
                }
            }

            context.Result = string.Format("A total of {0} following suggestions sent to {1} people", followingSuggestionsSuggestionsTotal, followingSuggestionsEmailsSent);

            if (exceptionMsgs.Any())
            {
                throw new Exception("One or more exceptions occurred calculating suggestions..." + Environment.NewLine + exceptionMsgs.AsDelimited(Environment.NewLine));
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Execute( IJobExecutionContext context )
        {
            var exceptionMsgs = new List<string>();

            JobDataMap dataMap = context.JobDetail.JobDataMap;
            Guid? groupGuid = dataMap.GetString( "EligibleFollowers" ).AsGuidOrNull();
            Guid? systemEmailGuid = dataMap.GetString( "EmailTemplate" ).AsGuidOrNull();
            int followingSuggestionsEmailsSent = 0;
            int followingSuggestionsSuggestionsTotal = 0;

            if ( groupGuid.HasValue && systemEmailGuid.HasValue )
            {
                using ( var rockContext = new RockContext() )
                {
                    var followingService = new FollowingService( rockContext );

                    // The people who are eligible to get following suggestions based on the group type setting for this job
                    var eligiblePersonIds = new GroupMemberService( rockContext )
                        .Queryable().AsNoTracking()
                        .Where( m =>
                            m.Group != null &&
                            m.Group.Guid.Equals( groupGuid.Value ) &&
                            m.GroupMemberStatus == GroupMemberStatus.Active &&
                            m.Person != null &&
                            m.Person.Email != null &&
                            m.Person.Email != "" )
                        .Select( m => m.PersonId )
                        .Distinct();

                    // check to see if there are any event types that require notification
                    var followerPersonIds = new List<int>();
                    if ( new FollowingEventTypeService( rockContext )
                        .Queryable().AsNoTracking()
                        .Any( e => e.IsNoticeRequired ) )
                    {
                        // if so, include all eligible people
                        followerPersonIds = eligiblePersonIds.ToList();
                    }
                    else
                    {
                        // if not, filter the list of eligible people down to only those that actually have subscribed to one or more following events
                        followerPersonIds = new FollowingEventSubscriptionService( rockContext )
                            .Queryable().AsNoTracking()
                            .Where( f => eligiblePersonIds.Contains( f.PersonAlias.PersonId ) )
                            .Select( f => f.PersonAlias.PersonId )
                            .Distinct()
                            .ToList();
                    }

                    if ( followerPersonIds.Any() )
                    {
                        // Get the primary person alias id for each of the followers
                        var primaryAliasIds = new Dictionary<int, int>();
                        new PersonAliasService( rockContext )
                            .Queryable().AsNoTracking()
                            .Where( a =>
                                followerPersonIds.Contains( a.PersonId ) &&
                                a.PersonId == a.AliasPersonId )
                            .ToList()
                            .ForEach( a => primaryAliasIds.AddOrIgnore( a.PersonId, a.Id ) );

                        // Get current date/time.
                        var timestamp = RockDateTime.Now;

                        var suggestionTypes = new FollowingSuggestionTypeService( rockContext )
                            .Queryable().AsNoTracking()
                            .Where( s => s.IsActive )
                            .OrderBy( s => s.Name )
                            .ToList();

                        var components = new Dictionary<int, SuggestionComponent>();
                        var suggestedEntities = new Dictionary<int, Dictionary<int, IEntity>>();

                        foreach ( var suggestionType in suggestionTypes )
                        {
                            try
                            {
                                // Get the suggestion type component
                                var suggestionComponent = suggestionType.GetSuggestionComponent();
                                if ( suggestionComponent != null )
                                {
                                    components.Add( suggestionType.Id, suggestionComponent );

                                    // Get the entitytype for this suggestion type
                                    var suggestionEntityType = EntityTypeCache.Read( suggestionComponent.FollowedType );
                                    if ( suggestionEntityType != null )
                                    {
                                        var entityIds = new List<int>();

                                        // Call the components method to return all of it's suggestions
                                        var personEntitySuggestions = suggestionComponent.GetSuggestions( suggestionType, followerPersonIds );

                                        // If any suggestions were returned by the component
                                        if ( personEntitySuggestions.Any() )
                                        {
                                            int entityTypeId = suggestionEntityType.Id;
                                            string reasonNote = suggestionType.ReasonNote;

                                            // Get the existing followings for any of the followers
                                            var existingFollowings = new Dictionary<int, List<int>>();
                                            foreach( var following in followingService.Queryable( "PersonAlias" ).AsNoTracking()
                                                .Where( f =>
                                                    f.EntityTypeId == entityTypeId &&
                                                    followerPersonIds.Contains( f.PersonAlias.PersonId ) ) )
                                            {
                                                existingFollowings.AddOrIgnore( following.PersonAlias.PersonId, new List<int>() );
                                                existingFollowings[ following.PersonAlias.PersonId].Add( following.EntityId );
                                            }

                                            // Loop through each follower
                                            foreach ( var followerPersonId in personEntitySuggestions
                                                .Select( s => s.PersonId )
                                                .Distinct() )
                                            {

                                                using ( var suggestionContext = new RockContext() )
                                                {
                                                    var followingSuggestedService = new FollowingSuggestedService( suggestionContext );

                                                    // Read all the existing suggestions for this type and the returned followers
                                                    var existingSuggestions = followingSuggestedService
                                                        .Queryable( "PersonAlias" )
                                                        .Where( s =>
                                                            s.SuggestionTypeId == suggestionType.Id &&
                                                            s.PersonAlias.PersonId == followerPersonId )
                                                        .ToList();

                                                    // Look  through the returned suggestions
                                                    foreach ( var followedEntityId in personEntitySuggestions
                                                        .Where( s => s.PersonId == followerPersonId )
                                                        .Select( s => s.EntityId ) )
                                                    {
                                                        // Make sure person isn't already following this entity
                                                        if ( !existingFollowings.ContainsKey( followerPersonId )
                                                            || !existingFollowings[followerPersonId].Contains( followedEntityId ) )
                                                        {
                                                            // If this person had a primary alias id
                                                            if ( primaryAliasIds.ContainsKey( followerPersonId ) )
                                                            {
                                                                entityIds.Add( followedEntityId );

                                                                // Look for existing suggestion for this person and entity
                                                                var suggestion = existingSuggestions
                                                                    .Where( s => s.EntityId == followedEntityId )
                                                                    .OrderByDescending( s => s.StatusChangedDateTime )
                                                                    .FirstOrDefault();

                                                                // If not found, add one
                                                                if ( suggestion == null )
                                                                {
                                                                    suggestion = new FollowingSuggested();
                                                                    suggestion.EntityTypeId = entityTypeId;
                                                                    suggestion.EntityId = followedEntityId;
                                                                    suggestion.PersonAliasId = primaryAliasIds[followerPersonId];
                                                                    suggestion.SuggestionTypeId = suggestionType.Id;
                                                                    suggestion.Status = FollowingSuggestedStatus.PendingNotification;
                                                                    suggestion.StatusChangedDateTime = timestamp;
                                                                    followingSuggestedService.Add( suggestion );
                                                                }
                                                                else
                                                                {
                                                                    // If found, and it has not been ignored, and it's time to promote again, update the promote date
                                                                    if ( suggestion.Status != FollowingSuggestedStatus.Ignored &&
                                                                        (
                                                                            !suggestionType.ReminderDays.HasValue ||
                                                                            !suggestion.LastPromotedDateTime.HasValue ||
                                                                            suggestion.LastPromotedDateTime.Value.AddDays( suggestionType.ReminderDays.Value ) <= timestamp
                                                                        ) )
                                                                    {
                                                                        if ( suggestion.Status != FollowingSuggestedStatus.PendingNotification )
                                                                        {
                                                                            suggestion.StatusChangedDateTime = timestamp;
                                                                            suggestion.Status = FollowingSuggestedStatus.PendingNotification;
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }

                                                    // Save the suggestions for this type
                                                    suggestionContext.SaveChanges();
                                                }
                                            }
                                        }

                                        // If any entities are being suggested for this type, query database for them and save to dictionary
                                        if ( entityIds.Any() )
                                        {
                                            if ( suggestionEntityType.AssemblyName != null )
                                            {
                                                // get the actual type of what is being followed
                                                Type entityType = suggestionEntityType.GetEntityType();
                                                if ( entityType != null )
                                                {
                                                    // Get generic queryable method and query all the entities that are being followed
                                                    Type[] modelType = { entityType };
                                                    Type genericServiceType = typeof( Rock.Data.Service<> );
                                                    Type modelServiceType = genericServiceType.MakeGenericType( modelType );
                                                    Rock.Data.IService serviceInstance = Activator.CreateInstance( modelServiceType, new object[] { rockContext } ) as IService;
                                                    MethodInfo qryMethod = serviceInstance.GetType().GetMethod( "Queryable", new Type[] { } );
                                                    var entityQry = qryMethod.Invoke( serviceInstance, new object[] { } ) as IQueryable<IEntity>;
                                                    var entityList = entityQry.AsNoTracking().Where( q => entityIds.Contains( q.Id ) ).ToList();
                                                    if ( entityList != null && entityList.Any() )
                                                    {
                                                        var entities = new Dictionary<int, IEntity>();
                                                        entityList.ForEach( e => entities.Add( e.Id, e ) );
                                                        suggestedEntities.Add( suggestionType.Id, entities );
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            catch ( Exception ex )
                            {
                                exceptionMsgs.Add( string.Format( "An exception occurred calculating suggestions for the '{0}' suggestion type:{1}    {2}", suggestionType.Name, Environment.NewLine, ex.Messages().AsDelimited( Environment.NewLine + "   " ) ) );
                                ExceptionLogService.LogException( ex, System.Web.HttpContext.Current );
                            }
                        }

                        var allSuggestions = new FollowingSuggestedService( rockContext )
                            .Queryable( "PersonAlias" )
                            .Where( s => s.Status == FollowingSuggestedStatus.PendingNotification )
                            .ToList();

                        var suggestionPersonIds = allSuggestions
                            .Where( s => followerPersonIds.Contains( s.PersonAlias.PersonId ) )
                            .Select( s => s.PersonAlias.PersonId )
                            .Distinct()
                            .ToList();

                        var appRoot = Rock.Web.Cache.GlobalAttributesCache.Read( rockContext ).GetValue( "PublicApplicationRoot" );

                        foreach ( var person in new PersonService( rockContext )
                            .Queryable().AsNoTracking()
                            .Where( p => suggestionPersonIds.Contains( p.Id ) )
                            .ToList() )
                        {
                            try
                            {
                                var personSuggestionNotices = new List<FollowingSuggestionNotices>();

                                foreach ( var suggestionType in suggestionTypes )
                                {
                                    var component = components.ContainsKey( suggestionType.Id ) ? components[suggestionType.Id] : null;
                                    if ( component != null && suggestedEntities.ContainsKey( suggestionType.Id ) )
                                    {

                                        var entities = new List<IEntity>();
                                        foreach ( var suggestion in allSuggestions
                                            .Where( s =>
                                                s.PersonAlias.PersonId == person.Id &&
                                                s.SuggestionTypeId == suggestionType.Id )
                                            .ToList() )
                                        {
                                            if ( suggestedEntities[suggestionType.Id].ContainsKey( suggestion.EntityId ) )
                                            {
                                                entities.Add( suggestedEntities[suggestionType.Id][suggestion.EntityId] );
                                                suggestion.LastPromotedDateTime = timestamp;
                                                suggestion.Status = FollowingSuggestedStatus.Suggested;
                                            }
                                        }

                                        var notices = new List<string>();
                                        foreach ( var entity in component.SortEntities( entities ) )
                                        {
                                            notices.Add( component.FormatEntityNotification( suggestionType, entity ) );
                                        }

                                        if ( notices.Any() )
                                        {
                                            personSuggestionNotices.Add( new FollowingSuggestionNotices( suggestionType, notices ) );
                                        }
                                    }
                                }

                                if ( personSuggestionNotices.Any() )
                                {
                                    // Send the notice
                                    var recipients = new List<RecipientData>();
                                    var mergeFields = new Dictionary<string, object>();
                                    mergeFields.Add( "Person", person );
                                    mergeFields.Add( "Suggestions", personSuggestionNotices.OrderBy( s => s.SuggestionType.Order ).ToList() );
                                    recipients.Add( new RecipientData( person.Email, mergeFields ) );
                                    Email.Send( systemEmailGuid.Value, recipients, appRoot );
                                    followingSuggestionsEmailsSent += recipients.Count();
                                    followingSuggestionsSuggestionsTotal += personSuggestionNotices.Count();
                                }

                                rockContext.SaveChanges();
                            }
                            catch ( Exception ex )
                            {
                                exceptionMsgs.Add( string.Format( "An exception occurred sending suggestions to '{0}':{1}    {2}", person.FullName, Environment.NewLine, ex.Messages().AsDelimited( Environment.NewLine + "   " ) ) );
                                ExceptionLogService.LogException( ex, System.Web.HttpContext.Current );
                            }
                        }

                    }
                }
            }

            context.Result = string.Format( "A total of {0} following suggestions sent to {1} people", followingSuggestionsSuggestionsTotal, followingSuggestionsEmailsSent );

            if ( exceptionMsgs.Any() )
            {
                throw new Exception( "One or more exceptions occurred calculating suggestions..." + Environment.NewLine + exceptionMsgs.AsDelimited( Environment.NewLine ) );
            }
        }