/// <summary>
        /// Get cache key hashes for both the query and the settings.
        /// </summary>
        /// <remarks>
        /// Note: we need to do both in the same context, so that a single StructuredQueryHashingContext can do all of the GUID normalizations together.
        /// </remarks>
        /// <returns>A hash string</returns>
        private static string GetCacheKeyTokens(StructuredQuery query, QuerySqlBuilderSettings settings)
        {
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }

            using (new StructuredQueryHashingContext( ))
            {
                string queryHash = query.CacheKeyToken();

                byte[] clientAggregateBytes = null;

                if (settings.ClientAggregate != null)
                {
                    clientAggregateBytes = settings.ClientAggregate.Serialize( );
                }

                byte[] sharedParameterBytes = null;

                if (settings.SharedParameters != null)
                {
                    using (var memoryStream = new MemoryStream( ))
                    {
                        // Serialize the query
                        Serializer.Serialize(memoryStream, settings.SharedParameters);
                        memoryStream.Flush( );
                        sharedParameterBytes = memoryStream.ToArray( );
                    }
                }

                byte[] hashValue = HashValues(clientAggregateBytes, sharedParameterBytes);

                string settingsHash = string.Empty;

                if (hashValue != null)
                {
                    settingsHash = Convert.ToBase64String(hashValue);
                }

                string key = string.Concat(queryHash, " / ", settingsHash);
                return(key);
            }
        }
        public void Test_GetCacheKeyToken_Stability( )
        {
            StructuredQuery sq1 = ReportHelpers.BuildFilterQuery("Name='test1'", new ReadiNow.Model.EntityRef("test:person"), true);

            sq1.SelectColumns.Add(new SelectColumn {
                Expression = new ResourceExpression(sq1.RootEntity, "core:name")
            });

            StructuredQuery sq2 = ReportHelpers.BuildFilterQuery("Name='test1'", new ReadiNow.Model.EntityRef("test:person"), true);

            sq2.SelectColumns.Add(new SelectColumn {
                Expression = new ResourceExpression(sq2.RootEntity, "core:name")
            });

            string token1 = sq1.CacheKeyToken( );
            string token2 = sq2.CacheKeyToken( );

            Assert.That(token1, Is.EqualTo(token2).And.Not.Null);
        }
        public void Test_GetCacheKeyToken_Concurrent( )
        {
            StructuredQuery sq1 = ReportHelpers.BuildFilterQuery("Name='test1'", new ReadiNow.Model.EntityRef("test:person"), true);

            sq1.SelectColumns.Add(new SelectColumn {
                Expression = new ResourceExpression(sq1.RootEntity, "core:name")
            });

            StructuredQuery sq2 = ReportHelpers.BuildFilterQuery("Name='test1'", new ReadiNow.Model.EntityRef("test:person"), true);

            sq2.SelectColumns.Add(new SelectColumn {
                Expression = new ResourceExpression(sq2.RootEntity, "core:name")
            });

            string token1 = null;
            string token2 = null;

            Task.WaitAll(
                Task.Factory.StartNew(() => { token1 = sq1.CacheKeyToken( ); }),
                Task.Factory.StartNew(() => { token2 = sq2.CacheKeyToken( ); })
                );

            Assert.That(token1, Is.EqualTo(token2).And.Not.Null);
        }