You've already forked linux-packaging-mono
Imported Upstream version 4.6.0.125
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
This commit is contained in:
parent
a569aebcfd
commit
e79aa3c0ed
@ -0,0 +1,121 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="CompiledQueryCacheEntry.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//
|
||||
// @owner [....]
|
||||
// @backupOwner [....]
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.Common.QueryCache
|
||||
{
|
||||
using System;
|
||||
using System.Data.Metadata.Edm;
|
||||
using System.Data.Objects;
|
||||
using System.Data.Objects.Internal;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a compiled LINQ ObjectQuery cache entry
|
||||
/// </summary>
|
||||
internal sealed class CompiledQueryCacheEntry : QueryCacheEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The merge option that was inferred during expression conversion.
|
||||
/// </summary>
|
||||
public readonly MergeOption? PropagatedMergeOption;
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary that contains a plan for each combination of
|
||||
/// merge option and UseCSharpNullComparisonBehavior flag.
|
||||
/// </summary>
|
||||
private ConcurrentDictionary<String, ObjectQueryExecutionPlan> _plans;
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// constructor
|
||||
/// </summary>
|
||||
/// <param name="queryCacheKey">The cache key that targets this cache entry</param>
|
||||
/// <param name="mergeOption">The inferred merge option that applies to this cached query</param>
|
||||
internal CompiledQueryCacheEntry(QueryCacheKey queryCacheKey, MergeOption? mergeOption)
|
||||
: base(queryCacheKey, null)
|
||||
{
|
||||
this.PropagatedMergeOption = mergeOption;
|
||||
_plans = new ConcurrentDictionary<string,ObjectQueryExecutionPlan>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods/Properties
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the execution plan for the specified merge option and UseCSharpNullComparisonBehavior flag. May return null if the
|
||||
/// plan for the given merge option and useCSharpNullComparisonBehavior flag is not present.
|
||||
/// </summary>
|
||||
/// <param name="mergeOption">The merge option for which an execution plan is required.</param>
|
||||
/// <param name="useCSharpNullComparisonBehavior">Flag indicating if C# behavior should be used for null comparisons.</param>
|
||||
/// <returns>The corresponding execution plan, if it exists; otherwise <c>null</c>.</returns>
|
||||
internal ObjectQueryExecutionPlan GetExecutionPlan(MergeOption mergeOption, bool useCSharpNullComparisonBehavior)
|
||||
{
|
||||
string key = GenerateLocalCacheKey(mergeOption, useCSharpNullComparisonBehavior);
|
||||
ObjectQueryExecutionPlan plan;
|
||||
_plans.TryGetValue(key, out plan);
|
||||
return plan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to set the execution plan for <paramref name="newPlan"/>'s merge option and <paramref name="useCSharpNullComparisonBehavior"/> flag on
|
||||
/// this cache entry to <paramref name="newPlan"/>. If a plan already exists for that merge option and UseCSharpNullComparisonBehavior flag, the
|
||||
/// current value is not changed but is returned to the caller. Otherwise <paramref name="newPlan"/> is returned to the caller.
|
||||
/// </summary>
|
||||
/// <param name="newPlan">The new execution plan to add to this cache entry.</param>
|
||||
/// <param name="useCSharpNullComparisonBehavior">Flag indicating if C# behavior should be used for null comparisons.</param>
|
||||
/// <returns>The execution plan that corresponds to <paramref name="newPlan"/>'s merge option, which may be <paramref name="newPlan"/> or may be a previously added execution plan.</returns>
|
||||
internal ObjectQueryExecutionPlan SetExecutionPlan(ObjectQueryExecutionPlan newPlan, bool useCSharpNullComparisonBehavior)
|
||||
{
|
||||
Debug.Assert(newPlan != null, "New plan cannot be null");
|
||||
|
||||
string planKey = GenerateLocalCacheKey(newPlan.MergeOption, useCSharpNullComparisonBehavior);
|
||||
// Get the value if it is there. If not, add it and get it.
|
||||
return (_plans.GetOrAdd(planKey, newPlan));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to retrieve the result type from the first non-null execution plan found on this cache entry.
|
||||
/// </summary>
|
||||
/// <param name="resultType">The result type of any execution plan that is or could be added to this cache entry</param>
|
||||
/// <returns><c>true</c> if at least one execution plan was present and a result type could be retrieved; otherwise <c>false</c></returns>
|
||||
internal bool TryGetResultType(out TypeUsage resultType)
|
||||
{
|
||||
foreach (var value in _plans.Values)
|
||||
{
|
||||
resultType = value.ResultType;
|
||||
return true;
|
||||
}
|
||||
resultType = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal override object GetTarget()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
private string GenerateLocalCacheKey(MergeOption mergeOption, bool useCSharpNullComparisonBehavior)
|
||||
{
|
||||
switch (mergeOption)
|
||||
{
|
||||
case MergeOption.AppendOnly:
|
||||
case MergeOption.NoTracking:
|
||||
case MergeOption.OverwriteChanges:
|
||||
case MergeOption.PreserveChanges:
|
||||
return string.Join("", Enum.GetName(typeof(MergeOption), mergeOption), useCSharpNullComparisonBehavior);
|
||||
default:
|
||||
throw EntityUtil.ArgumentOutOfRange("newPlan.MergeOption");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="CompiledQueryCacheKey.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//
|
||||
// @owner [....]
|
||||
// @backupOwner [....]
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.Common.QueryCache
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
internal sealed class CompiledQueryCacheKey : QueryCacheKey
|
||||
{
|
||||
private readonly Guid _cacheIdentity;
|
||||
|
||||
internal CompiledQueryCacheKey(Guid cacheIdentity)
|
||||
{
|
||||
_cacheIdentity = cacheIdentity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines equality of this key with respect to <paramref name="compareTo"/>
|
||||
/// </summary>
|
||||
/// <param name="otherObject"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Equals(object compareTo)
|
||||
{
|
||||
Debug.Assert(compareTo != null, "Comparison key should not be null");
|
||||
if (typeof(CompiledQueryCacheKey) != compareTo.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((CompiledQueryCacheKey)compareTo)._cacheIdentity.Equals(this._cacheIdentity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hashcode for this cache key
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _cacheIdentity.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the state of this cache key
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A string representation that includes query text, parameter information, include path information
|
||||
/// and merge option information about this cache key.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return _cacheIdentity.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="EntityClientCacheKey.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//
|
||||
// @owner [....]
|
||||
// @backupOwner [....]
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.Common.QueryCache
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common.Internal;
|
||||
using System.Data.EntityClient;
|
||||
using System.Data.Metadata.Edm;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Represents EntityCommand Cache key context
|
||||
/// </summary>
|
||||
internal sealed class EntityClientCacheKey : QueryCacheKey
|
||||
{
|
||||
/// <summary>
|
||||
/// Stored procedure or command text?
|
||||
/// </summary>
|
||||
readonly CommandType _commandType;
|
||||
|
||||
/// <summary>
|
||||
/// Entity Sql statement
|
||||
/// </summary>
|
||||
readonly string _eSqlStatement;
|
||||
|
||||
/// <summary>
|
||||
/// parameter collection token
|
||||
/// </summary>
|
||||
readonly string _parametersToken;
|
||||
|
||||
/// <summary>
|
||||
/// number of parameters
|
||||
/// </summary>
|
||||
readonly int _parameterCount;
|
||||
|
||||
/// <summary>
|
||||
/// Combined Hashcode based on field hashcodes
|
||||
/// </summary>
|
||||
readonly int _hashCode;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of EntityClientCacheKey given a entityCommand instance
|
||||
/// </summary>
|
||||
/// <param name="entityCommand"></param>
|
||||
internal EntityClientCacheKey(EntityCommand entityCommand)
|
||||
: base()
|
||||
{
|
||||
// Command Type
|
||||
_commandType = entityCommand.CommandType;
|
||||
|
||||
// Statement
|
||||
_eSqlStatement = entityCommand.CommandText;
|
||||
|
||||
// Parameters
|
||||
_parametersToken = GetParametersToken(entityCommand);
|
||||
_parameterCount = entityCommand.Parameters.Count;
|
||||
|
||||
// Hashcode
|
||||
_hashCode = _commandType.GetHashCode() ^
|
||||
_eSqlStatement.GetHashCode() ^
|
||||
_parametersToken.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// determines equality of two cache keys based on cache context values
|
||||
/// </summary>
|
||||
/// <param name="otherObject"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Equals( object otherObject )
|
||||
{
|
||||
Debug.Assert(null != otherObject, "otherObject must not be null");
|
||||
if (typeof(EntityClientCacheKey) != otherObject.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EntityClientCacheKey otherEntityClientCacheKey = (EntityClientCacheKey)otherObject;
|
||||
|
||||
return (_commandType == otherEntityClientCacheKey._commandType &&
|
||||
_parameterCount == otherEntityClientCacheKey._parameterCount) &&
|
||||
Equals(otherEntityClientCacheKey._eSqlStatement, _eSqlStatement) &&
|
||||
Equals(otherEntityClientCacheKey._parametersToken, _parametersToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Context Hash Code
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _hashCode;
|
||||
}
|
||||
|
||||
private static string GetTypeUsageToken(TypeUsage type)
|
||||
{
|
||||
string result = null;
|
||||
|
||||
// Dev10#537010: EntityCommand false positive cache hits caused by insufficient parameter type information in cache key
|
||||
// Ensure String types are correctly differentiated.
|
||||
if (object.ReferenceEquals(type, DbTypeMap.AnsiString))
|
||||
{
|
||||
result = "AnsiString";
|
||||
}
|
||||
else if (object.ReferenceEquals(type, DbTypeMap.AnsiStringFixedLength))
|
||||
{
|
||||
result = "AnsiStringFixedLength";
|
||||
}
|
||||
else if (object.ReferenceEquals(type, DbTypeMap.String))
|
||||
{
|
||||
result = "String";
|
||||
}
|
||||
else if (object.ReferenceEquals(type, DbTypeMap.StringFixedLength))
|
||||
{
|
||||
result = "StringFixedLength";
|
||||
}
|
||||
else if (object.ReferenceEquals(type, DbTypeMap.Xml))
|
||||
{
|
||||
// Xml is currently mapped to (unicode, variable-length) string, so the TypeUsage
|
||||
// given to the provider is actually a String TypeUsage.
|
||||
Debug.Assert(TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.String), "Update GetTypeUsageToken to return 'Xml' for Xml parameters");
|
||||
result = "String";
|
||||
}
|
||||
else if (TypeSemantics.IsEnumerationType(type))
|
||||
{
|
||||
result = type.EdmType.FullName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// String/Xml TypeUsages are the only DbType-derived TypeUsages that carry meaningful facets.
|
||||
// Otherwise, the primitive type name is a sufficient token (note that full name is not required
|
||||
// since model types always have the 'Edm' namespace).
|
||||
Debug.Assert(TypeSemantics.IsPrimitiveType(type), "EntityParameter TypeUsage not a primitive type?");
|
||||
Debug.Assert(!TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.String), "String TypeUsage not derived from DbType.AnsiString, AnsiString, String, StringFixedLength or Xml?");
|
||||
result = type.EdmType.Name;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the parameter list
|
||||
/// </summary>
|
||||
/// <param name="entityCommand"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetParametersToken(EntityCommand entityCommand)
|
||||
{
|
||||
if (null == entityCommand.Parameters || 0 == entityCommand.Parameters.Count)
|
||||
{
|
||||
//
|
||||
// means no parameters
|
||||
//
|
||||
return "@@0";
|
||||
}
|
||||
|
||||
// Ensure that parameter DbTypes are valid and there are no duplicate names
|
||||
Dictionary<string, TypeUsage> paramTypeUsage = entityCommand.GetParameterTypeUsage();
|
||||
Debug.Assert(paramTypeUsage.Count == entityCommand.Parameters.Count, "entityParameter collection and query parameter collection must have the same number of entries");
|
||||
if (1 == paramTypeUsage.Count)
|
||||
{
|
||||
// if its one parameter only, there is no need to use stringbuilder
|
||||
return "@@1:" +
|
||||
entityCommand.Parameters[0].ParameterName + ":" +
|
||||
GetTypeUsageToken(paramTypeUsage[entityCommand.Parameters[0].ParameterName]);
|
||||
}
|
||||
else
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(entityCommand.Parameters.Count * EstimatedParameterStringSize);
|
||||
Debug.Assert(paramTypeUsage.Count == entityCommand.Parameters.Count, "entityParameter collection and query parameter collection must have the same number of entries");
|
||||
sb.Append("@@");
|
||||
sb.Append(entityCommand.Parameters.Count);
|
||||
sb.Append(":");
|
||||
string separator = "";
|
||||
foreach (KeyValuePair<string, TypeUsage> param in paramTypeUsage)
|
||||
{
|
||||
sb.Append(separator);
|
||||
sb.Append(param.Key);
|
||||
sb.Append(":");
|
||||
sb.Append(GetTypeUsageToken(param.Value));
|
||||
separator = ";";
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the composed cache key
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Join("|", new string[] { Enum.GetName(typeof(CommandType), _commandType), _eSqlStatement, _parametersToken });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="EntitySqlQueryCacheKey.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//
|
||||
// @owner [....]
|
||||
// @backupOwner [....]
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.Common.QueryCache
|
||||
{
|
||||
using System;
|
||||
using System.Data.Objects;
|
||||
using System.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Entity-SQL-based ObjectQuery Cache key context
|
||||
/// </summary>
|
||||
internal sealed class EntitySqlQueryCacheKey : QueryCacheKey
|
||||
{
|
||||
/// <summary>
|
||||
/// Aggregate hashcode based the hashcode of the properties of this cache key
|
||||
/// </summary>
|
||||
private readonly int _hashCode;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the default container in effect when the Entity-SQL text was parsed
|
||||
/// (affects whether or not the text can be successfully parsed)
|
||||
/// </summary>
|
||||
private string _defaultContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Entity Sql statement
|
||||
/// </summary>
|
||||
private readonly string _eSqlStatement;
|
||||
|
||||
/// <summary>
|
||||
/// Parameter collection token
|
||||
/// </summary>
|
||||
private readonly string _parametersToken;
|
||||
|
||||
/// <summary>
|
||||
/// Number of parameters
|
||||
/// </summary>
|
||||
private readonly int _parameterCount;
|
||||
|
||||
/// <summary>
|
||||
/// Concatenated representation of the Include span paths
|
||||
/// </summary>
|
||||
private readonly string _includePathsToken;
|
||||
|
||||
/// <summary>
|
||||
/// The merge option in effect
|
||||
/// </summary>
|
||||
private readonly MergeOption _mergeOption;
|
||||
|
||||
/// <summary>
|
||||
/// Result type affects assembly plan.
|
||||
/// </summary>
|
||||
private readonly Type _resultType;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of ObjectQueryCacheKey given a entityCommand instance
|
||||
/// </summary>
|
||||
/// <param name="defaultContainerName">The default container name in effect when parsing the query (may be null)</param>
|
||||
/// <param name="eSqlStatement">The Entity-SQL text of the query</param>
|
||||
/// <param name="parameterCount">The number of parameters to the query</param>
|
||||
/// <param name="parametersToken">A string representation of the parameters to the query (may be null)</param>
|
||||
/// <param name="includePathsToken">A string representation of the Include span paths in effect (may be null)</param>
|
||||
/// <param name="mergeOption">The merge option in effect. Required for result assembly.</param>
|
||||
internal EntitySqlQueryCacheKey(string defaultContainerName,
|
||||
string eSqlStatement,
|
||||
int parameterCount,
|
||||
string parametersToken,
|
||||
string includePathsToken,
|
||||
MergeOption mergeOption,
|
||||
Type resultType)
|
||||
: base()
|
||||
{
|
||||
Debug.Assert(null != eSqlStatement, "eSqlStatement must not be null");
|
||||
|
||||
_defaultContainer = defaultContainerName;
|
||||
_eSqlStatement = eSqlStatement;
|
||||
_parameterCount = parameterCount;
|
||||
_parametersToken = parametersToken;
|
||||
_includePathsToken = includePathsToken;
|
||||
_mergeOption = mergeOption;
|
||||
_resultType = resultType;
|
||||
|
||||
int combinedHash = _eSqlStatement.GetHashCode() ^
|
||||
_mergeOption.GetHashCode();
|
||||
|
||||
if (_parametersToken != null)
|
||||
{
|
||||
combinedHash ^= _parametersToken.GetHashCode();
|
||||
}
|
||||
|
||||
if (_includePathsToken != null)
|
||||
{
|
||||
combinedHash ^= _includePathsToken.GetHashCode();
|
||||
}
|
||||
|
||||
if (_defaultContainer != null)
|
||||
{
|
||||
combinedHash ^= _defaultContainer.GetHashCode();
|
||||
}
|
||||
|
||||
_hashCode = combinedHash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines equality of two cache keys based on cache context values
|
||||
/// </summary>
|
||||
/// <param name="otherObject"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Equals(object otherObject)
|
||||
{
|
||||
Debug.Assert(null != otherObject, "otherObject must not be null");
|
||||
if (typeof(EntitySqlQueryCacheKey) != otherObject.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EntitySqlQueryCacheKey otherObjectQueryCacheKey = (EntitySqlQueryCacheKey)otherObject;
|
||||
|
||||
// also use result type...
|
||||
return (_parameterCount == otherObjectQueryCacheKey._parameterCount) &&
|
||||
(_mergeOption == otherObjectQueryCacheKey._mergeOption) &&
|
||||
Equals(otherObjectQueryCacheKey._defaultContainer, _defaultContainer) &&
|
||||
Equals(otherObjectQueryCacheKey._eSqlStatement, _eSqlStatement) &&
|
||||
Equals(otherObjectQueryCacheKey._includePathsToken, _includePathsToken) &&
|
||||
Equals(otherObjectQueryCacheKey._parametersToken, _parametersToken) &&
|
||||
Equals(otherObjectQueryCacheKey._resultType, _resultType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hashcode for this cache key
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _hashCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the state of this cache key
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A string representation that includes query text, parameter information, include path information
|
||||
/// and merge option information about this cache key.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Join("|", new string[] { _defaultContainer, _eSqlStatement, _parametersToken, _includePathsToken, Enum.GetName(typeof(MergeOption), _mergeOption) });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="LinqQueryCacheKey.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//
|
||||
// @owner [....]
|
||||
// @backupOwner venkatja
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.Common.QueryCache
|
||||
{
|
||||
using System;
|
||||
using System.Data.Objects;
|
||||
using System.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an ELinq-based ObjectQuery Cache key context
|
||||
/// </summary>
|
||||
internal sealed class LinqQueryCacheKey : QueryCacheKey
|
||||
{
|
||||
/// <summary>
|
||||
/// Aggregate hashcode based the hashcode of the properties of this cache key
|
||||
/// </summary>
|
||||
private readonly int _hashCode;
|
||||
|
||||
/// <summary>
|
||||
/// DbExpression key
|
||||
/// </summary>
|
||||
private readonly string _expressionKey;
|
||||
|
||||
/// <summary>
|
||||
/// Parameter collection token
|
||||
/// </summary>
|
||||
private readonly string _parametersToken;
|
||||
|
||||
/// <summary>
|
||||
/// Number of parameters
|
||||
/// </summary>
|
||||
private readonly int _parameterCount;
|
||||
|
||||
/// <summary>
|
||||
/// Concatenated representation of the Include span paths
|
||||
/// </summary>
|
||||
private readonly string _includePathsToken;
|
||||
|
||||
/// <summary>
|
||||
/// The merge option in effect
|
||||
/// </summary>
|
||||
private readonly MergeOption _mergeOption;
|
||||
|
||||
/// <summary>
|
||||
/// Result type affects assembly plan.
|
||||
/// </summary>
|
||||
private readonly Type _resultType;
|
||||
|
||||
/// <summary>
|
||||
/// Flag indicating if the C# behavior should be used for null comparisons
|
||||
/// </summary>
|
||||
private readonly bool _useCSharpNullComparisonBehavior;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of LinqQueryCacheKey.
|
||||
/// </summary>
|
||||
/// <param name="expressionKey">The DbExpression key of the linq query</param>
|
||||
/// <param name="parameterCount">The number of parameters to the query</param>
|
||||
/// <param name="parametersToken">A string representation of the parameters to the query (may be null)</param>
|
||||
/// <param name="includePathsToken">A string representation of the Include span paths in effect (may be null)</param>
|
||||
/// <param name="mergeOption">The merge option in effect. Required for result assembly.</param>
|
||||
/// <param name="useCSharpNullComparisonBehavior">Flag indicating if the C# behavior should be used for null comparisons</param>
|
||||
/// <param name="resultType">The type of each result item - for a given query as a CLR type instance</param>
|
||||
internal LinqQueryCacheKey(string expressionKey,
|
||||
int parameterCount,
|
||||
string parametersToken,
|
||||
string includePathsToken,
|
||||
MergeOption mergeOption,
|
||||
bool useCSharpNullComparisonBehavior,
|
||||
Type resultType)
|
||||
: base()
|
||||
{
|
||||
Debug.Assert(null != expressionKey, "expressionKey must not be null");
|
||||
|
||||
_expressionKey = expressionKey;
|
||||
_parameterCount = parameterCount;
|
||||
_parametersToken = parametersToken;
|
||||
_includePathsToken = includePathsToken;
|
||||
_mergeOption = mergeOption;
|
||||
_resultType = resultType;
|
||||
_useCSharpNullComparisonBehavior = useCSharpNullComparisonBehavior;
|
||||
|
||||
int combinedHash = _expressionKey.GetHashCode() ^
|
||||
_mergeOption.GetHashCode();
|
||||
|
||||
if (_parametersToken != null)
|
||||
{
|
||||
combinedHash ^= _parametersToken.GetHashCode();
|
||||
}
|
||||
|
||||
if (_includePathsToken != null)
|
||||
{
|
||||
combinedHash ^= _includePathsToken.GetHashCode();
|
||||
}
|
||||
|
||||
combinedHash ^= _useCSharpNullComparisonBehavior.GetHashCode();
|
||||
|
||||
_hashCode = combinedHash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines equality of two cache keys based on cache context values
|
||||
/// </summary>
|
||||
public override bool Equals(object otherObject)
|
||||
{
|
||||
Debug.Assert(null != otherObject, "otherObject must not be null");
|
||||
if (typeof(LinqQueryCacheKey) != otherObject.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LinqQueryCacheKey otherObjectQueryCacheKey = (LinqQueryCacheKey)otherObject;
|
||||
|
||||
// also use result type...
|
||||
return (_parameterCount == otherObjectQueryCacheKey._parameterCount) &&
|
||||
(_mergeOption == otherObjectQueryCacheKey._mergeOption) &&
|
||||
Equals(otherObjectQueryCacheKey._expressionKey, _expressionKey) &&
|
||||
Equals(otherObjectQueryCacheKey._includePathsToken, _includePathsToken) &&
|
||||
Equals(otherObjectQueryCacheKey._parametersToken, _parametersToken) &&
|
||||
Equals(otherObjectQueryCacheKey._resultType, _resultType) &&
|
||||
Equals(otherObjectQueryCacheKey._useCSharpNullComparisonBehavior, _useCSharpNullComparisonBehavior);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hashcode for this cache key
|
||||
/// </summary>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _hashCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the state of this cache key
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Join("|", new string[] { _expressionKey, _parametersToken, _includePathsToken, Enum.GetName(typeof(MergeOption), _mergeOption), _useCSharpNullComparisonBehavior.ToString() });
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="QueryCacheEntry.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//
|
||||
// @owner [....]
|
||||
// @backupOwner [....]
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.Common.QueryCache
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the abstract base class for all cache entry values in the query cache
|
||||
/// </summary>
|
||||
internal class QueryCacheEntry
|
||||
{
|
||||
#region Fields
|
||||
/// <summary>
|
||||
/// querycachekey for this entry
|
||||
/// </summary>
|
||||
readonly private QueryCacheKey _queryCacheKey;
|
||||
|
||||
/// <summary>
|
||||
/// strong reference to the target object
|
||||
/// </summary>
|
||||
readonly protected object _target;
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// cache entry constructor
|
||||
/// </summary>
|
||||
/// <param name="queryCacheKey"></param>
|
||||
/// <param name="target"></param>
|
||||
internal QueryCacheEntry(QueryCacheKey queryCacheKey, object target)
|
||||
{
|
||||
_queryCacheKey = queryCacheKey;
|
||||
_target = target;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods and Properties
|
||||
/// <summary>
|
||||
/// The payload of this cache entry.
|
||||
/// </summary>
|
||||
internal virtual object GetTarget()
|
||||
{
|
||||
return _target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the query cache key
|
||||
/// </summary>
|
||||
internal QueryCacheKey QueryCacheKey
|
||||
{
|
||||
get { return _queryCacheKey; }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="QueryCacheKey.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//
|
||||
// @owner [....]
|
||||
// @backupOwner [....]
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.Common.QueryCache
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// represents an abstract cache key
|
||||
/// </summary>
|
||||
internal abstract class QueryCacheKey
|
||||
{
|
||||
#region Constants
|
||||
protected const int EstimatedParameterStringSize = 20;
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
/// <summary>
|
||||
/// entry hit counter
|
||||
/// </summary>
|
||||
private uint _hitCount;
|
||||
|
||||
/// <summary>
|
||||
/// aging index
|
||||
/// </summary>
|
||||
private int _agingIndex;
|
||||
|
||||
/// <summary>
|
||||
/// default string comparison kind - Ordinal
|
||||
/// </summary>
|
||||
protected static StringComparison _stringComparison = StringComparison.Ordinal;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
protected QueryCacheKey()
|
||||
{
|
||||
_hitCount = 1;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Abstract Methods
|
||||
/// <summary>
|
||||
/// Determines whether two instances of QueryCacheContext are equal.
|
||||
/// Equality is value based.
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public abstract override bool Equals( object obj );
|
||||
|
||||
/// <summary>
|
||||
/// Returns QueryCacheContext instance HashCode
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract override int GetHashCode();
|
||||
#endregion
|
||||
|
||||
#region Internal API
|
||||
/// <summary>
|
||||
/// Cache entry hit count
|
||||
/// </summary>
|
||||
internal uint HitCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hitCount;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_hitCount = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets Aging index for cache entry
|
||||
/// </summary>
|
||||
internal int AgingIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return _agingIndex;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_agingIndex = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates hit count
|
||||
/// </summary>
|
||||
internal void UpdateHit()
|
||||
{
|
||||
if (uint.MaxValue != _hitCount)
|
||||
{
|
||||
unchecked { _hitCount++; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// default string comparer
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <param name="t"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool Equals( string s, string t )
|
||||
{
|
||||
return String.Equals(s, t, _stringComparison);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,465 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="QueryCacheManager.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//
|
||||
// @owner [....]
|
||||
// @backupOwner [....]
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.Common.QueryCache
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.EntityClient;
|
||||
using System.Data.Metadata.Edm;
|
||||
using System.Data.Objects.Internal;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Data.Common.Internal.Materialization;
|
||||
|
||||
/// <summary>
|
||||
/// Provides Query Execution Plan Caching Service
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Thread safe.
|
||||
/// Dispose <b>must</b> be called as there is no finalizer for this class
|
||||
/// </remarks>
|
||||
internal class QueryCacheManager : IDisposable
|
||||
{
|
||||
#region Constants/Default values for configuration parameters
|
||||
|
||||
/// <summary>
|
||||
/// Default Soft maximum number of entries in the cache
|
||||
/// Default value: 1000
|
||||
/// </summary>
|
||||
const int DefaultMaxNumberOfEntries = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Default high mark for starting sweeping process
|
||||
/// default value: 80% of MaxNumberOfEntries
|
||||
/// </summary>
|
||||
const float DefaultHighMarkPercentageFactor = 0.8f; // 80% of MaxLimit
|
||||
|
||||
/// <summary>
|
||||
/// Recycler timer period
|
||||
/// </summary>
|
||||
const int DefaultRecyclerPeriodInMilliseconds = 60 * 1000;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// cache lock object
|
||||
/// </summary>
|
||||
private readonly object _cacheDataLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// cache data
|
||||
/// </summary>
|
||||
private readonly Dictionary<QueryCacheKey, QueryCacheEntry> _cacheData = new Dictionary<QueryCacheKey, QueryCacheEntry>(32);
|
||||
|
||||
/// <summary>
|
||||
/// soft maximum number of entries in the cache
|
||||
/// </summary>
|
||||
private readonly int _maxNumberOfEntries;
|
||||
|
||||
/// <summary>
|
||||
/// high mark of the number of entries to trigger the sweeping process
|
||||
/// </summary>
|
||||
private readonly int _sweepingTriggerHighMark;
|
||||
|
||||
/// <summary>
|
||||
/// Eviction timer
|
||||
/// </summary>
|
||||
private readonly EvictionTimer _evictionTimer;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction and Initialization
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new Query Cache Manager instance, with default values for all 'configurable' parameters.
|
||||
/// </summary>
|
||||
/// <returns>A new instance of <see cref="QueryCacheManager"/> configured with default entry count, load factor and recycle period</returns>
|
||||
internal static QueryCacheManager Create()
|
||||
{
|
||||
return new QueryCacheManager(DefaultMaxNumberOfEntries, DefaultHighMarkPercentageFactor, DefaultRecyclerPeriodInMilliseconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cache Constructor
|
||||
/// </summary>
|
||||
/// <param name="maximumSize">
|
||||
/// Maximum number of entries that the cache should contain.
|
||||
/// </param>
|
||||
/// <param name="loadFactor">
|
||||
/// The number of entries that must be present, as a percentage, before entries should be removed
|
||||
/// according to the eviction policy.
|
||||
/// Must be greater than 0 and less than or equal to 1.0
|
||||
/// </param>
|
||||
/// <param name="recycleMillis">
|
||||
/// The interval, in milliseconds, at which the number of entries will be compared to the load factor
|
||||
/// and eviction carried out if necessary.
|
||||
/// </param>
|
||||
private QueryCacheManager(int maximumSize, float loadFactor, int recycleMillis)
|
||||
{
|
||||
Debug.Assert(maximumSize > 0, "Maximum size must be greater than zero");
|
||||
Debug.Assert(loadFactor > 0 && loadFactor <= 1, "Load factor must be greater than 0.0 and less than or equal to 1.0");
|
||||
Debug.Assert(recycleMillis >= 0, "Recycle period in milliseconds must not be negative");
|
||||
|
||||
//
|
||||
// Load hardcoded defaults
|
||||
//
|
||||
this._maxNumberOfEntries = maximumSize;
|
||||
|
||||
//
|
||||
// set sweeping high mark trigger value
|
||||
//
|
||||
this._sweepingTriggerHighMark = (int)(_maxNumberOfEntries * loadFactor);
|
||||
|
||||
//
|
||||
// Initialize Recycler
|
||||
//
|
||||
this._evictionTimer = new EvictionTimer(this, recycleMillis);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 'External' interface
|
||||
/// <summary>
|
||||
/// Adds new entry to the cache using "abstract" cache context and
|
||||
/// value; returns an existing entry if the key is already in the
|
||||
/// dictionary.
|
||||
/// </summary>
|
||||
/// <param name="inQueryCacheEntry"></param>
|
||||
/// <param name="outQueryCacheEntry">
|
||||
/// The existing entry in the dicitionary if already there;
|
||||
/// inQueryCacheEntry if none was found and inQueryCacheEntry
|
||||
/// was added instead.
|
||||
/// </param>
|
||||
/// <returns>true if the output entry was already found; false if it had to be added.</returns>
|
||||
internal bool TryLookupAndAdd(QueryCacheEntry inQueryCacheEntry, out QueryCacheEntry outQueryCacheEntry)
|
||||
{
|
||||
Debug.Assert(null != inQueryCacheEntry, "qEntry must not be null");
|
||||
|
||||
outQueryCacheEntry = null;
|
||||
|
||||
lock (_cacheDataLock)
|
||||
{
|
||||
if (!_cacheData.TryGetValue(inQueryCacheEntry.QueryCacheKey, out outQueryCacheEntry))
|
||||
{
|
||||
//
|
||||
// add entry to cache data
|
||||
//
|
||||
_cacheData.Add(inQueryCacheEntry.QueryCacheKey, inQueryCacheEntry);
|
||||
if (_cacheData.Count > _sweepingTriggerHighMark)
|
||||
{
|
||||
_evictionTimer.Start();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
outQueryCacheEntry.QueryCacheKey.UpdateHit();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lookup service for a cached value.
|
||||
/// </summary>
|
||||
internal bool TryCacheLookup<TK, TE>(TK key, out TE value)
|
||||
where TK : QueryCacheKey
|
||||
{
|
||||
Debug.Assert(null != key, "key must not be null");
|
||||
|
||||
value = default(TE);
|
||||
|
||||
//
|
||||
// invoke internal lookup
|
||||
//
|
||||
QueryCacheEntry qEntry = null;
|
||||
bool bHit = TryInternalCacheLookup(key, out qEntry);
|
||||
|
||||
//
|
||||
// if it is a hit, 'extract' the entry strong type cache value
|
||||
//
|
||||
if (bHit)
|
||||
{
|
||||
value = (TE)qEntry.GetTarget();
|
||||
}
|
||||
|
||||
return bHit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the Cache
|
||||
/// </summary>
|
||||
internal void Clear()
|
||||
{
|
||||
lock (_cacheDataLock)
|
||||
{
|
||||
_cacheData.Clear();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Members
|
||||
|
||||
/// <summary>
|
||||
/// lookup service
|
||||
/// </summary>
|
||||
/// <param name="queryCacheKey"></param>
|
||||
/// <param name="queryCacheEntry"></param>
|
||||
/// <returns>true if cache hit, false if cache miss</returns>
|
||||
private bool TryInternalCacheLookup( QueryCacheKey queryCacheKey, out QueryCacheEntry queryCacheEntry )
|
||||
{
|
||||
Debug.Assert(null != queryCacheKey, "queryCacheKey must not be null");
|
||||
|
||||
queryCacheEntry = null;
|
||||
|
||||
bool bHit = false;
|
||||
|
||||
//
|
||||
// lock the cache for the minimal possible period
|
||||
//
|
||||
lock (_cacheDataLock)
|
||||
{
|
||||
bHit = _cacheData.TryGetValue(queryCacheKey, out queryCacheEntry);
|
||||
}
|
||||
|
||||
//
|
||||
// if cache hit
|
||||
//
|
||||
if (bHit)
|
||||
{
|
||||
//
|
||||
// update hit mark in cache key
|
||||
//
|
||||
queryCacheEntry.QueryCacheKey.UpdateHit();
|
||||
}
|
||||
|
||||
return bHit;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Recycler handler. This method is called directly by the eviction timer.
|
||||
/// It should take no action beyond invoking the <see cref="SweepCache"/> method on the
|
||||
/// cache manager instance passed as <paramref name="state"/>.
|
||||
/// </summary>
|
||||
/// <param name="state">The cache manager instance on which the 'recycle' handler should be invoked</param>
|
||||
private static void CacheRecyclerHandler(object state)
|
||||
{
|
||||
((QueryCacheManager)state).SweepCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aging factor
|
||||
/// </summary>
|
||||
private static readonly int[] _agingFactor = {1,1,2,4,8,16};
|
||||
private static readonly int AgingMaxIndex = _agingFactor.Length - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Sweeps the cache removing old unused entries.
|
||||
/// This method implements the query cache eviction policy.
|
||||
/// </summary>
|
||||
private void SweepCache()
|
||||
{
|
||||
if (!this._evictionTimer.Suspend())
|
||||
{
|
||||
// Return of false from .Suspend means that the manager and timer have been disposed.
|
||||
return;
|
||||
}
|
||||
|
||||
bool disabledEviction = false;
|
||||
lock (_cacheDataLock)
|
||||
{
|
||||
//
|
||||
// recycle only if entries exceeds the high mark factor
|
||||
//
|
||||
if (_cacheData.Count > _sweepingTriggerHighMark)
|
||||
{
|
||||
//
|
||||
// sweep the cache
|
||||
//
|
||||
uint evictedEntriesCount = 0;
|
||||
List<QueryCacheKey> cacheKeys = new List<QueryCacheKey>(_cacheData.Count);
|
||||
cacheKeys.AddRange(_cacheData.Keys);
|
||||
for (int i = 0; i < cacheKeys.Count; i++)
|
||||
{
|
||||
//
|
||||
// if entry was not used in the last time window, then evict the entry
|
||||
//
|
||||
if (0 == cacheKeys[i].HitCount)
|
||||
{
|
||||
_cacheData.Remove(cacheKeys[i]);
|
||||
evictedEntriesCount++;
|
||||
}
|
||||
//
|
||||
// otherwise, age the entry in a progressive scheme
|
||||
//
|
||||
else
|
||||
{
|
||||
int agingIndex = unchecked(cacheKeys[i].AgingIndex + 1);
|
||||
if (agingIndex > AgingMaxIndex)
|
||||
{
|
||||
agingIndex = AgingMaxIndex;
|
||||
}
|
||||
cacheKeys[i].AgingIndex = agingIndex;
|
||||
cacheKeys[i].HitCount = cacheKeys[i].HitCount >> _agingFactor[agingIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_evictionTimer.Stop();
|
||||
disabledEviction = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!disabledEviction)
|
||||
{
|
||||
this._evictionTimer.Resume();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
/// <summary>
|
||||
/// Dispose instance
|
||||
/// </summary>
|
||||
/// <remarks>Dispose <b>must</b> be called as there are no finalizers for this class</remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
// Technically, calling GC.SuppressFinalize is not required because the class does not
|
||||
// have a finalizer, but it does no harm, protects against the case where a finalizer is added
|
||||
// in the future, and prevents an FxCop warning.
|
||||
GC.SuppressFinalize(this);
|
||||
if (this._evictionTimer.Stop())
|
||||
{
|
||||
this.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Periodically invokes cache cleanup logic on a specified <see cref="QueryCacheManager"/> instance,
|
||||
/// and allows this periodic callback to be suspended, resumed or stopped in a thread-safe way.
|
||||
/// </summary>
|
||||
private sealed class EvictionTimer
|
||||
{
|
||||
/// <summary>Used to control multi-threaded accesses to this instance</summary>
|
||||
private readonly object _sync = new object();
|
||||
|
||||
/// <summary>The required interval between invocations of the cache cleanup logic</summary>
|
||||
private readonly int _period;
|
||||
|
||||
/// <summary>The underlying QueryCacheManger that the callback will act on</summary>
|
||||
private readonly QueryCacheManager _cacheManager;
|
||||
|
||||
/// <summary>The underlying <see cref="Timer"/> that implements the periodic callback</summary>
|
||||
private Timer _timer;
|
||||
|
||||
internal EvictionTimer(QueryCacheManager cacheManager, int recyclePeriod)
|
||||
{
|
||||
this._cacheManager = cacheManager;
|
||||
this._period = recyclePeriod;
|
||||
}
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_timer == null)
|
||||
{
|
||||
this._timer = new Timer(QueryCacheManager.CacheRecyclerHandler, _cacheManager, _period, _period);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permanently stops the eviction timer.
|
||||
/// It will no longer generate periodic callbacks and further calls to <see cref="Suspend"/>, <see cref="Resume"/>, or <see cref="Stop"/>,
|
||||
/// though thread-safe, will have no effect.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// If this eviction timer has already been stopped (using the <see cref="Stop"/> method), returns <c>false</c>;
|
||||
/// otherwise, returns <c>true</c> to indicate that the call successfully stopped and cleaned up the underlying timer instance.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Thread safe. May be called regardless of the current state of the eviction timer.
|
||||
/// Once stopped, an eviction timer cannot be restarted with the <see cref="Resume"/> method.
|
||||
/// </remarks>
|
||||
internal bool Stop()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (this._timer != null)
|
||||
{
|
||||
this._timer.Dispose();
|
||||
this._timer = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses the operation of the eviction timer.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// If this eviction timer has already been stopped (using the <see cref="Stop"/> method), returns <c>false</c>;
|
||||
/// otherwise, returns <c>true</c> to indicate that the call successfully suspended the inderlying <see cref="Timer"/>
|
||||
/// and no further periodic callbacks will be generated until the <see cref="Resume"/> method is called.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Thread-safe. May be called regardless of the current state of the eviction timer.
|
||||
/// Once suspended, an eviction timer may be resumed or stopped.
|
||||
/// </remarks>
|
||||
internal bool Suspend()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (this._timer != null)
|
||||
{
|
||||
this._timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes this eviction timer to generate periodic callbacks, provided it has not been permanently stopped (using the <see cref="Stop"/> method).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Thread-safe. May be called regardless of the current state of the eviction timer.
|
||||
/// </remarks>
|
||||
internal void Resume()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (this._timer != null)
|
||||
{
|
||||
this._timer.Change(this._period, this._period);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="ShaperFactoryQueryCacheKey.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//
|
||||
// @owner [....]
|
||||
// @backupOwner [....]
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Data.Objects;
|
||||
namespace System.Data.Common.QueryCache
|
||||
{
|
||||
internal class ShaperFactoryQueryCacheKey<T> : QueryCacheKey
|
||||
{
|
||||
private readonly string _columnMapKey;
|
||||
private readonly MergeOption _mergeOption;
|
||||
private readonly bool _isValueLayer;
|
||||
|
||||
internal ShaperFactoryQueryCacheKey(string columnMapKey, MergeOption mergeOption, bool isValueLayer)
|
||||
{
|
||||
Debug.Assert(null != columnMapKey, "null columnMapKey");
|
||||
_columnMapKey = columnMapKey;
|
||||
_mergeOption = mergeOption;
|
||||
_isValueLayer = isValueLayer;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as ShaperFactoryQueryCacheKey<T>;
|
||||
if (null == other)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return this._columnMapKey.Equals(other._columnMapKey, _stringComparison)
|
||||
&& this._mergeOption == other._mergeOption
|
||||
&& this._isValueLayer == other._isValueLayer;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _columnMapKey.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user