1 //------------------------------------------------------------------------------
2 // <copyright file="EntityClientCacheKey.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //------------------------------------------------------------------------------
10 namespace System.Data.Common.QueryCache
13 using System.Collections.Generic;
14 using System.Data.Common.Internal;
15 using System.Data.EntityClient;
16 using System.Data.Metadata.Edm;
17 using System.Diagnostics;
18 using System.Globalization;
22 /// Represents EntityCommand Cache key context
24 internal sealed class EntityClientCacheKey : QueryCacheKey
27 /// Stored procedure or command text?
29 readonly CommandType _commandType;
32 /// Entity Sql statement
34 readonly string _eSqlStatement;
37 /// parameter collection token
39 readonly string _parametersToken;
42 /// number of parameters
44 readonly int _parameterCount;
47 /// Combined Hashcode based on field hashcodes
49 readonly int _hashCode;
52 /// Creates a new instance of EntityClientCacheKey given a entityCommand instance
54 /// <param name="entityCommand"></param>
55 internal EntityClientCacheKey(EntityCommand entityCommand)
59 _commandType = entityCommand.CommandType;
62 _eSqlStatement = entityCommand.CommandText;
65 _parametersToken = GetParametersToken(entityCommand);
66 _parameterCount = entityCommand.Parameters.Count;
69 _hashCode = _commandType.GetHashCode() ^
70 _eSqlStatement.GetHashCode() ^
71 _parametersToken.GetHashCode();
75 /// determines equality of two cache keys based on cache context values
77 /// <param name="otherObject"></param>
78 /// <returns></returns>
79 public override bool Equals( object otherObject )
81 Debug.Assert(null != otherObject, "otherObject must not be null");
82 if (typeof(EntityClientCacheKey) != otherObject.GetType())
87 EntityClientCacheKey otherEntityClientCacheKey = (EntityClientCacheKey)otherObject;
89 return (_commandType == otherEntityClientCacheKey._commandType &&
90 _parameterCount == otherEntityClientCacheKey._parameterCount) &&
91 Equals(otherEntityClientCacheKey._eSqlStatement, _eSqlStatement) &&
92 Equals(otherEntityClientCacheKey._parametersToken, _parametersToken);
96 /// Returns Context Hash Code
98 /// <returns></returns>
99 public override int GetHashCode()
104 private static string GetTypeUsageToken(TypeUsage type)
106 string result = null;
108 // Dev10#537010: EntityCommand false positive cache hits caused by insufficient parameter type information in cache key
109 // Ensure String types are correctly differentiated.
110 if (object.ReferenceEquals(type, DbTypeMap.AnsiString))
112 result = "AnsiString";
114 else if (object.ReferenceEquals(type, DbTypeMap.AnsiStringFixedLength))
116 result = "AnsiStringFixedLength";
118 else if (object.ReferenceEquals(type, DbTypeMap.String))
122 else if (object.ReferenceEquals(type, DbTypeMap.StringFixedLength))
124 result = "StringFixedLength";
126 else if (object.ReferenceEquals(type, DbTypeMap.Xml))
128 // Xml is currently mapped to (unicode, variable-length) string, so the TypeUsage
129 // given to the provider is actually a String TypeUsage.
130 Debug.Assert(TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.String), "Update GetTypeUsageToken to return 'Xml' for Xml parameters");
133 else if (TypeSemantics.IsEnumerationType(type))
135 result = type.EdmType.FullName;
139 // String/Xml TypeUsages are the only DbType-derived TypeUsages that carry meaningful facets.
140 // Otherwise, the primitive type name is a sufficient token (note that full name is not required
141 // since model types always have the 'Edm' namespace).
142 Debug.Assert(TypeSemantics.IsPrimitiveType(type), "EntityParameter TypeUsage not a primitive type?");
143 Debug.Assert(!TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.String), "String TypeUsage not derived from DbType.AnsiString, AnsiString, String, StringFixedLength or Xml?");
144 result = type.EdmType.Name;
151 /// Returns a string representation of the parameter list
153 /// <param name="entityCommand"></param>
154 /// <returns></returns>
155 private static string GetParametersToken(EntityCommand entityCommand)
157 if (null == entityCommand.Parameters || 0 == entityCommand.Parameters.Count)
160 // means no parameters
165 // Ensure that parameter DbTypes are valid and there are no duplicate names
166 Dictionary<string, TypeUsage> paramTypeUsage = entityCommand.GetParameterTypeUsage();
167 Debug.Assert(paramTypeUsage.Count == entityCommand.Parameters.Count, "entityParameter collection and query parameter collection must have the same number of entries");
168 if (1 == paramTypeUsage.Count)
170 // if its one parameter only, there is no need to use stringbuilder
172 entityCommand.Parameters[0].ParameterName + ":" +
173 GetTypeUsageToken(paramTypeUsage[entityCommand.Parameters[0].ParameterName]);
177 StringBuilder sb = new StringBuilder(entityCommand.Parameters.Count * EstimatedParameterStringSize);
178 Debug.Assert(paramTypeUsage.Count == entityCommand.Parameters.Count, "entityParameter collection and query parameter collection must have the same number of entries");
180 sb.Append(entityCommand.Parameters.Count);
182 string separator = "";
183 foreach (KeyValuePair<string, TypeUsage> param in paramTypeUsage)
185 sb.Append(separator);
186 sb.Append(param.Key);
188 sb.Append(GetTypeUsageToken(param.Value));
191 return sb.ToString();
196 /// returns the composed cache key
198 /// <returns></returns>
199 public override string ToString()
201 return String.Join("|", new string[] { Enum.GetName(typeof(CommandType), _commandType), _eSqlStatement, _parametersToken });