Exemplo n.º 1
0
        /// <summary>
        /// Check the cache.
        /// </summary>
        /// <param name="key">Key to look up.</param>
        /// <param name="result">The value result - either from cache or freshly determined.</param>
        /// <param name="valueFactory">A callback that will provide the values.</param>
        /// <returns></returns>

        protected bool TryGetOrAdd(TKey key, out TValue result, Func <TKey, TValue> valueFactory)
        {
            bool fromCache = false;

            // Wrap value-factory to handle cache invalidator
            Func <TKey, TValue> valueFactoryImpl = (k) =>
            {
                TValue innerResult;

                using (CacheContext cacheContext = new CacheContext())
                {
                    innerResult = valueFactory(key);

                    // Add the cache context entries to the appropriate CacheInvalidator
                    _cacheInvalidator.AddInvalidations(cacheContext, key);
                }

                return(innerResult);
            };

            // Check cache
            fromCache = Cache.TryGetOrAdd(key, out result, valueFactoryImpl);
            if (fromCache && CacheContext.IsSet())
            {
                // Add the already stored changes that should invalidate this cache
                // entry to any outer or containing cache contexts.
                using (CacheContext cacheContext = CacheContext.GetContext())
                {
                    cacheContext.AddInvalidationsFor(_cacheInvalidator, key);
                }
            }

            return(fromCache);
        }
        /// <summary>
        /// Load the roles recursively for a user or role.
        /// </summary>
        /// <param name="subjectId">
        /// The user to load the roles for. This cannot be negative.
        /// </param>
        /// <returns>
        /// The roles the user is recursively a member of.
        /// </returns>
        /// <exception cref="ArgumentException">
        /// <paramref name="subjectId"/> cannot be negative.
        /// </exception>
        public ISet <long> GetUserRoles(long subjectId)
        {
            ISet <long> roles;

            if (!Cache.TryGetValue(subjectId, out roles))
            {
                using (CacheContext cacheContext = new CacheContext())
                {
                    roles = RoleRepository.GetUserRoles(subjectId);

                    Cache[subjectId] = roles;

                    _cacheInvalidator.AddInvalidations(cacheContext, subjectId);
                }
            }
            else
            {
                // Add the already stored changes that should invalidate this cache
                // entry to any outer or containing cache contexts.
                using (CacheContext cacheContext = CacheContext.GetContext( ))
                {
                    cacheContext.AddInvalidationsFor(_cacheInvalidator, subjectId);
                }
            }

            return(roles);
        }
Exemplo n.º 3
0
 public void Test_AddInvalidationsFor_NullCacheInvalidator()
 {
     using (CacheContext cacheContext = new CacheContext())
     {
         Assert.That(() => cacheContext.AddInvalidationsFor <int, int>(null, 1),
                     Throws.TypeOf <ArgumentNullException>().And.Property("ParamName").EqualTo("cacheInvalidator"));
     }
 }
Exemplo n.º 4
0
        /// <summary>
        /// Execute a request for bulk data from the SQL database.
        /// </summary>
        /// <param name="request">The requested data</param>
        /// <returns></returns>
        public BulkRequestResult GetBulkResult(EntityRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            // Bypass cache
            if (request.IgnoreResultCache)
            {
                return(CreateResult(request));
            }

            BulkRequestResult             result;
            CachingBulkRequestRunnerValue cacheValue;
            CachingBulkRequestRunnerKey   key = CachingBulkRequestRunnerKey.Create(request);

            // Check cache
            bool inCache = Cache.TryGetValue(key, out cacheValue);

            // Should parent cache contexts be notified of invalidations
            // .. no for now, for compatibility with previous system. Consider changing
            bool notifyParentCacheContext = false;

            if (!inCache)
            {
                using (var cacheContext = new CacheContext(notifyParentCacheContext ? ContextType.New : ContextType.Detached))                      // Detached for now..
                {
                    result     = CreateResult(request);
                    cacheValue = new CachingBulkRequestRunnerValue(result);

                    Cache.Add(key, cacheValue);

                    // Add the cache context entries to the appropriate CacheInvalidator
                    cacheContext.Entities.Add(result.AllEntities.Keys);
                    cacheContext.EntityInvalidatingRelationshipTypes.Add(GetRelationshipTypesUsed(result));

                    _cacheInvalidator.AddInvalidations(cacheContext, key);
                }
            }
            else
            {
                if (notifyParentCacheContext && CacheContext.IsSet( ))
                {
                    using (CacheContext cacheContext = new CacheContext(ContextType.Attached))
                    {
                        // Add the already stored changes that should invalidate this cache
                        // entry to any outer or containing cache contexts.
                        cacheContext.AddInvalidationsFor(_cacheInvalidator, key);
                    }
                }
            }

            result = cacheValue.BulkRequestResult;
            request.ResultFromCache = inCache;  // TODO: Find a better channel to return this info. (It can't be in the response, because that's cached)

            return(result);
        }
Exemplo n.º 5
0
        /// <summary>
        /// Convert a <see cref="Report" /> to a <see cref="StructuredQuery" />.
        /// </summary>
        /// <param name="report">The <see cref="Report" /> to convert. This cannot be null.</param>
        /// <param name="settings"></param>
        /// <returns>
        /// The converted report.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">report</exception>
        public StructuredQuery Convert(Report report, ReportToQueryConverterSettings settings)
        {
            if (report == null)
            {
                throw new ArgumentNullException("report");
            }
            if (settings == null)
            {
                settings = ReportToQueryConverterSettings.Default;
            }

            StructuredQuery result;
            CachingReportToQueryConverterValue cacheValue;

            CachingReportToQueryConverterKey key = new CachingReportToQueryConverterKey(report, settings);

            using (MessageContext msg = new MessageContext("Reports"))
            {
                // Check cache
                bool doConvert = !TryGetValue(key, msg, out cacheValue);

                // Check for force recalculation
                if (settings.RefreshCachedStructuredQuery)
                {
                    msg.Append(() => "CachingReportToQueryConverter refreshed forced");
                    doConvert = true;
                }

                if (doConvert)
                {
                    lock (_syncRoot)
                    {
                        using (CacheContext cacheContext = new CacheContext( ))
                        {
                            result     = Converter.Convert(report, settings);
                            cacheValue = new CachingReportToQueryConverterValue(result);

                            Cache.Add(key, cacheValue);

                            // Add the cache context entries to the appropriate CacheInvalidator
                            _cacheInvalidator.AddInvalidations(cacheContext, key);
                        }
                    }
                }
                else if (CacheContext.IsSet( ))
                {
                    // Add the already stored changes that should invalidate this cache
                    // entry to any outer or containing cache contexts.
                    using (CacheContext cacheContext = CacheContext.GetContext( ))
                    {
                        cacheContext.AddInvalidationsFor(_cacheInvalidator, key);
                    }
                }
            }

            result = cacheValue.StructuredQuery;
            return(result);
        }
Exemplo n.º 6
0
        /// <summary>
        /// Build an <see cref="EntityMemberRequest"/> used to look for entities
        /// to perform additional security checks on.
        /// </summary>
        /// <param name="entityType">
        /// The type of entity whose security is being checked. This cannot be null.
        /// </param>
        /// <param name="permissions">The type of permissions required.</param>
        /// <returns>
        /// The <see cref="EntityMemberRequest"/>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="entityType"/> cannot be null.
        /// </exception>
        public EntityMemberRequest BuildEntityMemberRequest(EntityType entityType, IList <EntityRef> permissions)
        {
            if (entityType == null)
            {
                throw new ArgumentNullException("entityType");
            }

            bool isModify = false;

            if (permissions != null)
            {
                foreach (EntityRef perm in permissions)
                {
                    if (perm.Id == Permissions.Modify.Id || perm.Id == Permissions.Delete.Id)
                    {
                        isModify = true;
                        break;
                    }
                }
            }

            // negative keys for 'modify', +ve keys for read
            // because #27911 popped up just hours before the branch of a feature that got promised to someone
            long cacheKey = isModify ? -entityType.Id : entityType.Id;

            EntityMemberRequest result;

            if (!Cache.TryGetValue(cacheKey, out result))
            {
                using (CacheContext cacheContext = new CacheContext())
                {
                    result = Factory.BuildEntityMemberRequest(entityType, permissions);

                    Cache.Add(cacheKey, result);

                    _cacheInvalidator.AddInvalidations(cacheContext, cacheKey);
                }
            }
            else if (CacheContext.IsSet( ))
            {
                // Add the already stored changes that should invalidate this cache
                // entry to any outer or containing cache contexts.
                using (CacheContext cacheContext = CacheContext.GetContext( ))
                {
                    cacheContext.AddInvalidationsFor(_cacheInvalidator, cacheKey);
                }
            }

            return(result);
        }
        /// <summary>
        /// Gets a value that represents a hash of the IDs of a set of security rules.
        /// That is, if two users have equatable UserRuleSets then the same security rules apply to both.
        /// </summary>
        /// <param name="userId">
        /// The ID of the user <see cref="UserAccount"/>.
        /// This cannot be negative.
        /// </param>
        /// <param name="permission">
        /// The permission to get the query for. This cannot be null and should be one of <see cref="Permissions.Read"/>,
        /// <see cref="Permissions.Modify"/> or <see cref="Permissions.Delete"/>.
        /// </param>
        public UserRuleSet GetUserRuleSet(long userId, EntityRef permission)
        {
            // Validate
            if (userId <= 0)
            {
                throw new ArgumentNullException("userId");
            }
            if (permission == null)
            {
                throw new ArgumentNullException("permission");
            }
            if (permission.Id <= 0)
            {
                throw new ArgumentException("permission");
            }

            // Create cache key
            CachingUserRuleSetProviderKey key = new CachingUserRuleSetProviderKey(userId, permission.Id);
            UserRuleSet result;

            Func <CachingUserRuleSetProviderKey, UserRuleSet> valueFactory = (k) =>
            {
                UserRuleSet innerResult;

                using (CacheContext cacheContext = new CacheContext())
                {
                    innerResult = InnerProvider.GetUserRuleSet(k.SubjectId, k.PermissionId);

                    // Add the cache context entries to the appropriate CacheInvalidator
                    _cacheInvalidator.AddInvalidations(cacheContext, key);
                }

                return(innerResult);
            };

            // Check cache
            if (Cache.TryGetOrAdd(key, out result, valueFactory) &&
                CacheContext.IsSet())
            {
                // Add the already stored changes that should invalidate this cache
                // entry to any outer or containing cache contexts.
                using (CacheContext cacheContext = CacheContext.GetContext( ))
                {
                    cacheContext.AddInvalidationsFor(_cacheInvalidator, key);
                }
            }

            return(result);
        }
Exemplo n.º 8
0
        /// <summary>
        /// Get the queries for a given user and permission or operation.
        /// </summary>
        /// <param name="subjectId">
        /// The ID of the <see cref="Subject"/>, that is a <see cref="UserAccount"/> or <see cref="Role"/> instance.
        /// This cannot be negative.
        /// </param>
        /// <param name="permission">
        /// The permission to get the query for. This should be one of <see cref="Permissions.Read"/>,
        /// <see cref="Permissions.Modify"/> or <see cref="Permissions.Delete"/>. Or null to match all permissions.
        /// </param>
        /// <param name="securableEntityTypes">
        /// The IDs of <see cref="SecurableEntity"/> types being accessed. Or null to match all entity types.
        /// </param>
        /// <returns>
        /// The queries to run.
        /// </returns>
        /// <exception cref="ArgumentException">
        /// <paramref name="subjectId"/> does not exist. Also, <paramref name="permission"/> should
        /// be one of <see cref="Permissions.Read"/>, <see cref="Permissions.Modify"/> or <see cref="Permissions.Delete"/>
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// Neither <paramref name="permission"/> nor <paramref name="securableEntityTypes"/> can be null.
        /// </exception>
        public IEnumerable <AccessRuleQuery> GetQueries(long subjectId, [CanBeNull] Model.EntityRef permission, [CanBeNull] IList <long> securableEntityTypes)
        {
            SubjectPermissionTypesTuple   tuple;
            IEnumerable <AccessRuleQuery> result;

            result = null;
            tuple  = new SubjectPermissionTypesTuple(subjectId, permission, securableEntityTypes);

            if (!Cache.TryGetValue(tuple, out result))
            {
                using (CacheContext cacheContext = new CacheContext( ))
                {
                    result = Repository.GetQueries(subjectId, permission, securableEntityTypes).ToList( );
                    try
                    {
                        Cache.Add(tuple, result);

                        // Add the cache context entries to the appropriate CacheInvalidator
                        _cacheInvalidator.AddInvalidations(cacheContext, tuple);
                    }
                    catch (ArgumentException)
                    {
                        Trace.WriteLine("CachingQueryRepository: Entity already in cache");
                        throw;
                    }
                }
            }
            else if (CacheContext.IsSet( ))
            {
                using (CacheContext cacheContext = CacheContext.GetContext( ))
                {
                    // Add the already stored changes that should invalidate this cache
                    // entry to any outer or containing cache contexts.
                    cacheContext.AddInvalidationsFor(_cacheInvalidator, tuple);
                }
            }

            return(result);
        }
        /// <summary>
        /// Convert a <see cref="Report" /> to a <see cref="StructuredQuery" />.
        /// </summary>
        /// <param name="typeIds">The type ids.</param>
        /// <returns>
        /// The converted report.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">report</exception>
        public IEnumerable <Workflow> Fetch(ISet <long> typeIds)
        {
            if (typeIds == null)
            {
                throw new ArgumentNullException("typeIds");
            }


            CachingWorkflowActionsFactoryValue cacheValue;

            var key = new CachingWorkflowActionsFactoryKey(typeIds);

            // Check cache
            bool found = Cache.TryGetOrAdd(key, out cacheValue, (innerKey) =>
            {
                using (CacheContext cacheContext = new CacheContext( ))
                {
                    var result = new CachingWorkflowActionsFactoryValue(_fetcher.Fetch(innerKey.TypeIds));

                    // Add the cache context entries to the appropriate CacheInvalidator
                    _cacheInvalidator.AddInvalidations(cacheContext, key);
                    return(result);
                }
            }
                                           );

            if (found && CacheContext.IsSet( ))
            {
                // Add the already stored changes that should invalidate this cache
                // entry to any outer or containing cache contexts.
                using (CacheContext cacheContext = CacheContext.GetContext( ))
                {
                    cacheContext.AddInvalidationsFor(_cacheInvalidator, key);
                }
            }

            return(cacheValue.Workflows);
        }
Exemplo n.º 10
0
        /// <summary>
        /// Re-used cache checking logic that can be used for caching eithe instances lookups or type lookups.
        /// </summary>
        /// <param name="entities">The entities/types to check. This cannot be null or contain null.</param>
        /// <param name="permissions">The permissions or operations to check. This cannot be null or contain null.</param>
        /// <param name="user">The user requesting access. This cannot be null.</param>
        /// <param name="entityIdCallback">Callback used to determine how to get the ID of TEntity.</param>
        /// <param name="innerCheckAccessCallback">Callback used to perform the uncached check.</param>
        /// <returns>A mapping of each entity ID to whether the user has access (true) or not (false).</returns>
        private IDictionary <long, bool> CachedCheckImpl <TEntity>(IList <TEntity> entities, IList <EntityRef> permissions, EntityRef user,
                                                                   Func <TEntity, long> entityIdCallback,
                                                                   Func <IList <TEntity>, IList <EntityRef>, EntityRef, IDictionary <long, bool> > innerCheckAccessCallback)
        {
            TKey cacheKey;
            CachingEntityAccessControlCheckerResult result;
            IList <TEntity> toCheckEntities;

            long[] permissionIdArray;
            IDictionary <long, bool> innerResult;

            // If SecurityBypassContext is active, avoid the cache. Otherwise,
            // the cache will remember the user had access to entities that
            // they may not have.
            if (EntityAccessControlChecker.SkipCheck(user))
            {
                innerResult = innerCheckAccessCallback(entities, permissions, user);
                return(innerResult);
            }

            result            = new CachingEntityAccessControlCheckerResult();
            toCheckEntities   = null;
            permissionIdArray = permissions.Select(x => x.Id).ToArray();


            // Determine uncached entities
            using (CacheContext cacheContext = CacheContext.IsSet( ) ? CacheContext.GetContext( ) : null)
            {
                foreach (TEntity entity in entities)
                {
                    long entityId = entityIdCallback(entity);

                    cacheKey = CreateKey(user.Id, entityId, permissionIdArray);
                    bool cacheEntry;

                    if (Cache.TryGetValue(cacheKey, out cacheEntry))
                    {
                        result.CacheResult[entityId] = cacheEntry;

                        // Add the already stored changes that should invalidate this cache
                        // entry to any outer or containing cache contexts.
                        if (cacheContext != null)
                        {
                            cacheContext.AddInvalidationsFor(_cacheInvalidator, cacheKey);
                        }
                    }
                    else
                    {
                        if (toCheckEntities == null)
                        {
                            toCheckEntities = new List <TEntity>();
                        }
                        toCheckEntities.Add(entity);
                    }
                }
            }

            LogMessage(result);

            if (toCheckEntities != null)
            {
                using (CacheContext cacheContext = new CacheContext( ))
                {
                    innerResult = innerCheckAccessCallback(toCheckEntities, permissions, user);

                    foreach (KeyValuePair <long, bool> entry in innerResult)
                    {
                        long entityId      = entry.Key;
                        bool accessGranted = entry.Value;

                        result.Add(entityId, accessGranted);

                        // Cache the results
                        cacheKey         = CreateKey(user.Id, entry.Key, permissionIdArray);
                        Cache [cacheKey] = accessGranted;

                        // Add the cache context entries to the appropriate CacheInvalidator
                        _cacheInvalidator.AddInvalidations(cacheContext, cacheKey);
                    }
                }
            }

            // Add the results from the originally cached entities
            foreach (KeyValuePair <long, bool> entry in result.CacheResult)
            {
                result[entry.Key] = entry.Value;
            }

            return(result);
        }
Exemplo n.º 11
0
        /// <summary>
        /// Build the SQL, or collect it from cache.
        /// </summary>
        /// <param name="query"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        public QueryResult ExecuteQuery(StructuredQuery query, QuerySettings settings)
        {
            // Validate
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }
            if (QueryRunner == null)
            {
                throw new InvalidOperationException("QueryRunner not set.");
            }
            if (settings == null)
            {
                settings = new QuerySettings( );
            }

            // Determine if we should cache .. and the cache key
            QueryBuild            builtQuery;
            CachingQueryRunnerKey key;
            CacheContext          queryBuilderCacheContext;

            using (queryBuilderCacheContext = new CacheContext())
            {
                key = CreateCacheKeyAndQuery(query, settings, out builtQuery);
            }

            // A null key means that the ersult should not participate in caching
            if (key == null)
            {
                return(RunQueryImpl(query, settings, builtQuery));
            }

            CachingQueryRunnerValue cacheValue;

            using (MessageContext msg = new MessageContext("Reports"))
            {
                // Check for force recalculation
                if (settings.RefreshCachedResult)
                {
                    msg.Append(() => "CachingQueryRunner refreshed forced");
                    Cache.Remove(key);
                }

                // Run query
                bool fromCache = TryGetOrAdd(key, msg, out cacheValue, callbackKey =>
                {
                    using (CacheContext cacheContext = new CacheContext( ))
                    {
                        QueryResult queryResult = RunQueryImpl(query, settings, builtQuery);
                        cacheValue = new CachingQueryRunnerValue(query, queryResult);

                        // Add the cache context entries to the appropriate CacheInvalidator
                        _cacheInvalidator.AddInvalidations(cacheContext, callbackKey);
                        _cacheInvalidator.AddInvalidations(queryBuilderCacheContext, callbackKey);
                    }

                    return(cacheValue);
                });

                if (fromCache && CacheContext.IsSet())
                {
                    using (CacheContext cacheContext = CacheContext.GetContext( ))
                    {
                        // Add the already stored changes that should invalidate this cache
                        // entry to any outer or containing cache contexts.
                        cacheContext.AddInvalidationsFor(_cacheInvalidator, key);
                    }
                }
            }

            if (cacheValue == null)
            {
                throw new Exception("Assert false");
            }

            // Mutate returned result to be suitable for current query
            QueryResult result;

            if (cacheValue.OriginalQuery == query)
            {
                result = cacheValue.QueryResult;
            }
            else
            {
                result = MutateResultToMatchCurrentQuery(cacheValue, query);
            }

            return(result);
        }
Exemplo n.º 12
0
        public void Test_AddInvalidationsFor(long[] entities, long[] relationshipTypes, long[] fieldTypes, long[] entityInvalidatingRelationshipTypes)
        {
            CacheInvalidator <long, long> cacheInvalidator;
            const int    testKey = 1;
            IList <long> entityRefs;
            IList <long> relationshipTypeRefs;
            IList <long> fieldTypeRefs;
            IList <long> entityInvalidatingRelationshipTypeRefs;

            entityRefs           = entities.ToList();
            relationshipTypeRefs = relationshipTypes.ToList();
            fieldTypeRefs        = fieldTypes.ToList();
            entityInvalidatingRelationshipTypeRefs = entityInvalidatingRelationshipTypes.ToList();

            cacheInvalidator = new CacheInvalidator <long, long>(new DictionaryCache <long, long>(), "test");
            using (CacheContext originalCacheContext = new CacheContext())
            {
                originalCacheContext.Entities.Add(entityRefs);
                originalCacheContext.RelationshipTypes.Add(relationshipTypeRefs);
                originalCacheContext.FieldTypes.Add(fieldTypeRefs);
                originalCacheContext.EntityInvalidatingRelationshipTypes.Add(entityInvalidatingRelationshipTypeRefs);

                cacheInvalidator.AddInvalidations(originalCacheContext, testKey);
            }

            using (CacheContext outerCacheContext = new CacheContext())
                using (CacheContext innerCacheContext = new CacheContext())
                {
                    Assert.That(innerCacheContext.Entities, Is.Empty, "Entities not initially empty");
                    Assert.That(innerCacheContext.RelationshipTypes, Is.Empty, "RelationshipTypes not initially empty");
                    Assert.That(innerCacheContext.FieldTypes, Is.Empty, "FieldTypes not initially empty");
                    Assert.That(innerCacheContext.EntityInvalidatingRelationshipTypes, Is.Empty, "EntityInvalidatingRelationshipTypes not initially empty");

                    innerCacheContext.AddInvalidationsFor(cacheInvalidator, testKey);
                    Assert.That(innerCacheContext.Entities,
                                Is.EquivalentTo(entityRefs).Using(EntityRefComparer.Instance),
                                "Unexpected Entities");
                    Assert.That(innerCacheContext.RelationshipTypes,
                                Is.EquivalentTo(relationshipTypeRefs).Using(EntityRefComparer.Instance),
                                "Unexpected RelationshipTypes");
                    Assert.That(innerCacheContext.FieldTypes,
                                Is.EquivalentTo(fieldTypeRefs).Using(EntityRefComparer.Instance),
                                "Unexpected FieldTypes");
                    Assert.That(innerCacheContext.EntityInvalidatingRelationshipTypes,
                                Is.EquivalentTo(entityInvalidatingRelationshipTypeRefs).Using(EntityRefComparer.Instance),
                                "Unexpected EntityInvalidatingRelationshipTypes");

                    Assert.That(outerCacheContext.Entities,
                                Is.EquivalentTo(entityRefs).Using(EntityRefComparer.Instance),
                                "Unexpected Entities in outer cache context");
                    Assert.That(outerCacheContext.RelationshipTypes,
                                Is.EquivalentTo(relationshipTypeRefs).Using(EntityRefComparer.Instance),
                                "Unexpected RelationshipTypes in outer cache context");
                    Assert.That(outerCacheContext.FieldTypes,
                                Is.EquivalentTo(fieldTypeRefs).Using(EntityRefComparer.Instance),
                                "Unexpected FieldTypes in outer cache context");
                    Assert.That(outerCacheContext.EntityInvalidatingRelationshipTypes,
                                Is.EquivalentTo(entityInvalidatingRelationshipTypeRefs).Using(EntityRefComparer.Instance),
                                "Unexpected EntityInvalidatingRelationshipTypes in outer cache context");
                }
        }
Exemplo n.º 13
0
        /// <summary>
        /// Build the SQL, or collect it from cache.
        /// </summary>
        /// <param name="query"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        public QueryBuild BuildSql(StructuredQuery query, QuerySqlBuilderSettings settings)
        {
            // Validate
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }
            if (QuerySqlBuilder == null)
            {
                throw new InvalidOperationException("QuerySqlBuilder not set.");
            }
            if (settings == null)
            {
                settings = new QuerySqlBuilderSettings( );
            }

            // Check if query can even participate in cache
            if (!CachingQuerySqlBuilderKey.DoesRequestAllowForCaching(query, settings))
            {
                return(BuildSqlImpl(query, settings));
            }

            // Get a user-set key
            // (Users may share the same report SQL if they have the same set of read-rules)
            UserRuleSet userRuleSet = null;

            if (settings.RunAsUser != 0)
            {
                userRuleSet = UserRuleSetProvider.GetUserRuleSet(settings.RunAsUser, Permissions.Read);
                if (userRuleSet == null)
                {
                    throw new InvalidOperationException("Expected userRuleSet");   // Assert false
                }
            }

            // Create cache key
            CachingQuerySqlBuilderKey   key = new CachingQuerySqlBuilderKey(query, settings, userRuleSet);
            CachingQuerySqlBuilderValue cacheValue;

            using (MessageContext msg = new MessageContext("Reports"))
            {
                // Check for force recalculation
                if (settings.RefreshCachedSql)
                {
                    msg.Append(() => "CachingQuerySqlBuilder refreshed forced");
                    _cacheInvalidator.DebugInvalidations.Add(key);
                    Cache.Remove(key);
                }

                // In some circumstances, a result will be uncacheable, so we just return 'null' in the delegate instead.
                // However, on the first access, we will still be doing the calculation, so store it here.
                CachingQuerySqlBuilderValue calculatedOnThisAccess = null;

                // Check cache
                bool fromCache = TryGetOrAdd(key, msg, out cacheValue, callbackKey =>
                {
                    // This callback is called if we have a cache miss

                    using (CacheContext cacheContext = new CacheContext( ))
                    {
                        QueryBuild queryResult = BuildSqlImpl(query, settings);
                        cacheValue             = new CachingQuerySqlBuilderValue(query, queryResult);
                        calculatedOnThisAccess = cacheValue;

                        if (queryResult.SqlIsUncacheable)
                        {
                            return(null);
                        }
                        else
                        {
                            // Add the cache context entries to the appropriate CacheInvalidator
                            _cacheInvalidator.AddInvalidations(cacheContext, callbackKey);
                            return(cacheValue);
                        }
                    }
                });

                // cacheValue will be null if the result was uncacheable
                if (cacheValue == null)
                {
                    if (calculatedOnThisAccess != null)
                    {
                        // In this path, the result was uncacheable, so the cache returned a 'null',
                        // but it was the initial calculation run anyway, so we can get the value from callbackValue.
                        cacheValue = calculatedOnThisAccess;
                    }
                    else
                    {
                        // In this path, the result was uncacheable, but someone had asked previously, and stored
                        // the null, so we need to actually build the SQL again for this scenario.
                        // Note: don't need to do anything with cache context, because this cache is not participating.
                        // And if there's a parent context set, then the call to BuildSqlImpl will just talk directly to that context.
                        QueryBuild queryResult = BuildSqlImpl(query, settings);
                        cacheValue = new CachingQuerySqlBuilderValue(query, queryResult);
                    }
                }
                else if (fromCache && CacheContext.IsSet( ))
                {
                    // Add the already stored changes that should invalidate this cache
                    // entry to any outer or containing cache contexts.
                    using (CacheContext cacheContext = CacheContext.GetContext( ))
                    {
                        cacheContext.AddInvalidationsFor(_cacheInvalidator, key);
                    }
                }
            }

            if (cacheValue == null)
            {
                throw new Exception("Assert false");
            }

            // Mutate returned result to be suitable for current query
            QueryBuild result;

            if (cacheValue.OriginalQuery == query)
            {
                result = cacheValue.QueryResult;
            }
            else
            {
                result = MutateResultToMatchCurrentQuery(cacheValue, query);
            }

            return(result);
        }
Exemplo n.º 14
0
        /// <summary>
        /// Helper method to fetch multiple entries from cache, then only call the factory for missing entries.
        /// </summary>
        /// <remarks>
        /// Implementation notes:
        /// 1. The result is guaranteed to always be non-null
        /// 2. And contain the same entries as the keys in the same order.
        /// 3. All misses get evaluated in a single round
        /// 4. Caution: because all misses are evaluated together, they get bound together with the same invalidation.
        /// </remarks>
        /// <param name="keys"></param>
        /// <param name="valuesFactory"></param>
        /// <returns></returns>
        protected IReadOnlyCollection <KeyValuePair <TKey, TValue> > GetOrAddMultiple(IEnumerable <TKey> keys, Func <IEnumerable <TKey>, IEnumerable <TValue> > valuesFactory)
        {
            if (keys == null)
            {
                throw new ArgumentNullException("keys");
            }
            if (valuesFactory == null)
            {
                throw new ArgumentNullException("valuesFactory");
            }

            List <KeyValuePair <TKey, TValue> > result = new List <KeyValuePair <TKey, TValue> >();
            KeyValuePair <TKey, TValue>         resultEntry;

            // List of missing entries, including the index position where the result needs to be written
            List <Tuple <TKey, int> > cacheMisses = null;

            using (CacheContext cacheContext = CacheContext.IsSet() ? CacheContext.GetContext() : null)
            {
                // First pass through list
                int pos = 0;
                foreach (TKey key in keys)
                {
                    TValue value;

                    if (Cache.TryGetValue(key, out value))
                    {
                        resultEntry = new KeyValuePair <TKey, TValue>(key, value);

                        // Check cache
                        if (cacheContext != null)
                        {
                            cacheContext.AddInvalidationsFor(_cacheInvalidator, key);
                        }
                    }
                    else
                    {
                        if (cacheMisses == null)
                        {
                            cacheMisses = new List <Tuple <TKey, int> >();
                        }

                        cacheMisses.Add(new Tuple <TKey, int>(key, pos));

                        // An entry still gets added to the result to maintain ordering - it will be overridden later
                        // (can't use null .. kvp is a struct)
                        resultEntry = new KeyValuePair <TKey, TValue>(key, default(TValue));
                    }
                    result.Add(resultEntry);
                    pos++;
                }
            }

            // Fill in misses
            if (cacheMisses != null)
            {
                using (CacheContext cacheContext = new CacheContext())
                {
                    IEnumerable <TKey>   missingKeys    = cacheMisses.Select(k => k.Item1);
                    IEnumerable <TValue> missingResults = valuesFactory(missingKeys);

                    Enumerable.Zip(cacheMisses, missingResults, (keyPos, value) =>
                    {
                        TKey key        = keyPos.Item1;
                        int resultIndex = keyPos.Item2;
                        resultEntry     = new KeyValuePair <TKey, TValue>(key, value);

                        // Include in current result
                        result[resultIndex] = resultEntry;

                        // Add to cache
                        Cache.Add(key, value);

                        // Add the cache context entries to the appropriate CacheInvalidator
                        _cacheInvalidator.AddInvalidations(cacheContext, key);

                        return((object)null);
                    }).Last(); // call Last to force evaluate zip
                }
            }

            return(result);
        }