예제 #1
0
        /// <summary>
        /// Try to get the value from cache, with logging.
        /// </summary>
        private bool TryGetOrAdd(CachingQuerySqlBuilderKey key, MessageContext msg, out CachingQuerySqlBuilderValue result, Func <CachingQuerySqlBuilderKey, CachingQuerySqlBuilderValue> callback)
        {
            bool foundValue;

            foundValue = Cache.TryGetOrAdd(key, out result, callback);

            msg.Append(() => "CachingQuerySqlBuilder key:" + key);

            if (foundValue)
            {
                var cacheValue = result;
                msg.Append(() => "CachingQuerySqlBuilder cache hit");
                msg.Append(() => $"Entry originally cached at {cacheValue.CacheTime}");
            }
            else
            {
                msg.Append(() => "CachingQuerySqlBuilder cache miss");
            }

            return(foundValue);
        }
예제 #2
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);
        }
예제 #3
0
 /// <summary>
 /// The problem: we want to cache queries, but the StructuredQueries contain GUIDs that vary from call to call.
 /// The CachingQuerySqlBuilderKey happily handles this via StructuredQuery.CacheKeyToken. However, the result contains
 /// references back to the original query. And stuff down stream relies on having the correct GUIDs in the referenced columns.
 /// The solution: we take a shallowing copy of the result, and substutite in the columns from our current query.
 /// </summary>
 private static QueryBuild MutateResultToMatchCurrentQuery(CachingQuerySqlBuilderValue cacheValue, StructuredQuery currentQuery)
 {
     return(MutateResultToMatchCurrentQuery(cacheValue.QueryResult, cacheValue.OriginalQuery, currentQuery));
 }