private void BuildRoleMembersCache()
        {
            using (TicketDeskEntities ctx = new TicketDeskEntities())
            {
                var configuredTdRoles = new string[] { TdStaffRoleName, TdSubmittersRoleName, TdAdminRoleName };
                var sqlCacheMembers = ctx.AdCachedRoleMembers.ToList();//go ahead and fetch entire table
                foreach (var tdRole in configuredTdRoles)
                {
                    List<UserInfo> adUsersForTdRole = new List<UserInfo>();

                    adUsersForTdRole.AddRange(AdRepository.GetGroupMembersFromAd(tdRole));

                    foreach (var sMember in sqlCacheMembers.Where(rm => rm.GroupName == tdRole))//find roles in SQL that aren't in AD anymore
                    {
                        if (adUsersForTdRole.Count(au => au.Name == sMember.MemberName) < 1)
                        {
                            ctx.AdCachedRoleMembers.DeleteObject(sMember);
                        }
                    }
                    foreach (var aMember in adUsersForTdRole)// file roles in AD that need to be updated/added to SQL
                    {
                        var sMember = sqlCacheMembers.SingleOrDefault(sm => sm.GroupName == tdRole && sm.MemberName == aMember.Name);
                        if (sMember == null)//doesn't exist in SQL, insert
                        {
                            sMember = new AdCachedRoleMember();
                            sMember.GroupName = tdRole;
                            ctx.AdCachedRoleMembers.AddObject(sMember);
                        }
                        sMember.MemberName = aMember.Name;
                        sMember.MemberDisplayName = aMember.DisplayName;
                    }
                    ctx.SaveChanges();
                }
            }
        }
        /// <summary>
        /// Gets the user property from the SQL store.
        /// </summary>
        /// <remarks>
        /// If the SQL store doesn't contain data for the user/property requested it will
        /// add the user/property to the table so it gets fetched next time the system 
        /// refreshes from AD. In the meantime though, the method will return null.
        /// </remarks>
        /// <param name="userName">Name of the user.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns>The property stored in the SQL cache; null if no value cached</returns>
        internal string GetUserProperty(string userName, string propertyName)
        {
            userName = userName.ToLowerInvariant();
            string propertyValue = null;

            using (TicketDeskEntities ctx = new TicketDeskEntities())
            {
                var val = ctx.AdCachedUserProperties.FirstOrDefault(p => p.UserName == userName && p.PropertyName == propertyName);

                if (val == null)
                {
                    //add this property/user to the table for the next automated refresh
                    var newUserProp = new AdCachedUserProperty()
                                        {
                                            UserName = userName,
                                            PropertyName = propertyName,
                                            PropertyValue = null,
                                            LastRefreshed = null,
                                            IsActiveInAd = true
                                        };
                    ctx.AdCachedUserProperties.AddObject(newUserProp);
                }
                else
                {
                    propertyValue = val.PropertyValue;
                }
                ctx.SaveChanges();
            }

            return propertyValue;
        }
 private void BuildUserPropertiesCache(bool includeInactiveAd)
 {
     var refreshTime = DateTime.Now.AddMinutes((AdUserPropertiesSqlCacheRefreshMinutes * -1));
     using (TicketDeskEntities ctx = new TicketDeskEntities())
     {
         var propertiesForRefresh = ctx.AdCachedUserProperties.Where(up => !up.LastRefreshed.HasValue || up.LastRefreshed.Value <= refreshTime);
         if(!includeInactiveAd)
         { 
             propertiesForRefresh = propertiesForRefresh.Where(up => up.IsActiveInAd);
         }
         foreach(var property in propertiesForRefresh.ToList())
         {
             bool userFound;
             var value = AdRepository.GetUserPropertyFromAd(property.UserName, property.PropertyName, out userFound);
             property.IsActiveInAd = userFound;
             if (userFound)//if the user wasn't found, we don't alter the last value fetched (last fetched value lives forever)
             {
                 property.PropertyValue = value;
             }
             property.LastRefreshed = DateTime.Now;
             ctx.SaveChanges();
         }
         
     }
 }
        private void EnsureStandardPropertiesForAllKnownUsers()
        {
            var allKnownUsers = GetAllKnownDistinctUserNamesFrom();
            using (TicketDeskEntities ctx = new TicketDeskEntities())
            {
                //go ahead and fetch entire table. The table is kinda big, but realistically 
                //  there should only be two rows per valid user... even with a thousand users, 
                //  that's not an obsurde amount of data. 
                var existingUserProperties = ctx.AdCachedUserProperties.ToList();


                //loop 1 to add all display name properties for any users not already in the SQL cache

                var existingProps = existingUserProperties.Where(ep => ep.PropertyName == "displayName");

                foreach (var knownUser in allKnownUsers)
                {
                    if (existingProps.Count(ep => string.Equals(ep.UserName, knownUser, StringComparison.InvariantCultureIgnoreCase)) < 1)
                    {
                        var newUserProp = new AdCachedUserProperty()
                                            {
                                                UserName = knownUser,
                                                PropertyName = "displayName",
                                                IsActiveInAd = true
                                            };
                        ctx.AdCachedUserProperties.AddObject(newUserProp);
                    }
                }

                //loop 2 to add all email properties for any users not already in the SQL cache
                existingProps = existingUserProperties.Where(ep => ep.PropertyName == "mail");

                foreach (var knownUser in allKnownUsers)
                {
                    if (existingProps.Count(ep => string.Equals(ep.UserName, knownUser, StringComparison.InvariantCultureIgnoreCase)) < 1)
                    {
                        var newUserProp = new AdCachedUserProperty()
                                            {
                                                UserName = knownUser,
                                                PropertyName = "mail",
                                                IsActiveInAd = true
                                            };
                        ctx.AdCachedUserProperties.AddObject(newUserProp);
                    }


                   
                }
                ctx.SaveChanges();
            }
        }
        /// <summary>
        /// Ensures a valid next delivery date for any newly queud notifications when a notification has been rescheduled.
        /// </summary>
        /// <param name="ticketId">The ticket id.</param>
        /// <param name="notifyUser">The notify user.</param>
        /// <param name="lastSentCommentId">The last sent comment id.</param>
        /// <param name="nextDeliveryAttemptDate">The next delivery attempt date.</param>
        /// <remarks>
        /// While the system is processing ticket emails, new notifications could get queued up.
        /// This method should be called if the sending process has to re-schedule a notifiaciton 
        /// for a later re-try attempt. This will keep the notification that was being processed from
        /// having it's next delivery date kicked out further than newer comments that have queued up.
        /// It should look for any new notifications that are queued for the same user and ticket, 
        /// and ensure that their next-delivery date remains greater than the last one that was processed 
        /// and rescheduled.
        /// </remarks>
        public void EnsureValidNextDeliveryDateForNewlyQueudNotifications(int ticketId, string notifyUser, int lastSentCommentId, DateTime nextDeliveryAttemptDate)
        {

            using (var ctxNew = new TicketDeskEntities())
            {
                //the class's standard context will have cached data from any past reads. Do this on new context
                var newNotes = from tn in ctxNew.TicketEventNotifications
                               where tn.TicketId == ticketId && tn.NotifyUser == notifyUser && tn.CommentId > lastSentCommentId && tn.NextDeliveryAttemptDate <= nextDeliveryAttemptDate
                               select tn;
                if (newNotes.Count() > 0)
                {
                    int advanceSeconds = 1;
                    foreach (var newNote in newNotes)
                    {
                        newNote.NextDeliveryAttemptDate = nextDeliveryAttemptDate.AddSeconds(advanceSeconds);
                        advanceSeconds++;
                    }
                    ctxNew.SaveChanges();
                }
            }
        }