f779d4bc6c53dbf815d3deb328b5bee93afea4e1
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Common / QueryCache / EntityClientCacheKey.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="EntityClientCacheKey.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner  Microsoft
7 // @backupOwner Microsoft
8 //------------------------------------------------------------------------------
9
10 namespace System.Data.Common.QueryCache
11 {
12     using System;
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;
19     using System.Text;
20         
21     /// <summary>
22     /// Represents EntityCommand Cache key context
23     /// </summary>
24     internal sealed class EntityClientCacheKey : QueryCacheKey
25     {
26         /// <summary>
27         /// Stored procedure or command text?
28         /// </summary>
29         readonly CommandType _commandType;
30
31         /// <summary>
32         /// Entity Sql statement
33         /// </summary>
34         readonly string _eSqlStatement;
35
36         /// <summary>
37         /// parameter collection token
38         /// </summary>
39         readonly string _parametersToken;
40
41         /// <summary>
42         /// number of parameters
43         /// </summary>
44         readonly int _parameterCount;
45
46         /// <summary>
47         /// Combined Hashcode based on field hashcodes
48         /// </summary>
49         readonly int _hashCode;
50
51         /// <summary>
52         /// Creates a new instance of EntityClientCacheKey given a entityCommand instance
53         /// </summary>
54         /// <param name="entityCommand"></param>
55         internal EntityClientCacheKey(EntityCommand entityCommand)
56             : base()
57         {
58             // Command Type
59             _commandType = entityCommand.CommandType;
60
61             // Statement
62             _eSqlStatement = entityCommand.CommandText;
63             
64             // Parameters
65             _parametersToken = GetParametersToken(entityCommand);
66             _parameterCount = entityCommand.Parameters.Count;
67
68             // Hashcode
69             _hashCode = _commandType.GetHashCode() ^
70                         _eSqlStatement.GetHashCode() ^
71                         _parametersToken.GetHashCode();
72         }
73
74         /// <summary>
75         /// determines equality of two cache keys based on cache context values
76         /// </summary>
77         /// <param name="otherObject"></param>
78         /// <returns></returns>
79         public override bool Equals( object otherObject )
80         {
81             Debug.Assert(null != otherObject, "otherObject must not be null");
82             if (typeof(EntityClientCacheKey) != otherObject.GetType())
83             {
84                 return false;
85             }
86
87             EntityClientCacheKey otherEntityClientCacheKey = (EntityClientCacheKey)otherObject;
88
89             return (_commandType == otherEntityClientCacheKey._commandType &&
90                     _parameterCount == otherEntityClientCacheKey._parameterCount) &&
91                     Equals(otherEntityClientCacheKey._eSqlStatement, _eSqlStatement) &&
92                     Equals(otherEntityClientCacheKey._parametersToken, _parametersToken);
93         }
94
95         /// <summary>
96         /// Returns Context Hash Code
97         /// </summary>
98         /// <returns></returns>
99         public override int GetHashCode()
100         {
101             return _hashCode;
102         }
103
104         private static string GetTypeUsageToken(TypeUsage type)
105         {
106             string result = null;
107
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))
111             {
112                 result = "AnsiString";
113             }
114             else if (object.ReferenceEquals(type, DbTypeMap.AnsiStringFixedLength))
115             {
116                 result = "AnsiStringFixedLength";
117             }
118             else if (object.ReferenceEquals(type, DbTypeMap.String))
119             {
120                 result = "String";
121             }
122             else if (object.ReferenceEquals(type, DbTypeMap.StringFixedLength))
123             {
124                 result = "StringFixedLength";
125             }
126             else if (object.ReferenceEquals(type, DbTypeMap.Xml))
127             {
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");
131                 result = "String";
132             }
133             else if (TypeSemantics.IsEnumerationType(type))
134             {
135                 result = type.EdmType.FullName;
136             }
137             else
138             {
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;
145             }
146
147             return result;
148         }
149
150         /// <summary>
151         /// Returns a string representation of the parameter list
152         /// </summary>
153         /// <param name="entityCommand"></param>
154         /// <returns></returns>
155         private static string GetParametersToken(EntityCommand entityCommand)
156         {
157             if (null == entityCommand.Parameters || 0 == entityCommand.Parameters.Count)
158             {
159                 //
160                 // means no parameters
161                 //
162                 return "@@0";
163             }
164             
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)
169             {
170                 // if its one parameter only, there is no need to use stringbuilder
171                 return "@@1:" +
172                     entityCommand.Parameters[0].ParameterName + ":" +
173                     GetTypeUsageToken(paramTypeUsage[entityCommand.Parameters[0].ParameterName]);
174             }
175             else
176             {
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");
179                 sb.Append("@@");
180                 sb.Append(entityCommand.Parameters.Count);
181                 sb.Append(":");
182                 string separator = "";
183                 foreach (KeyValuePair<string, TypeUsage> param in paramTypeUsage)
184                 {
185                     sb.Append(separator);
186                     sb.Append(param.Key);
187                     sb.Append(":");
188                     sb.Append(GetTypeUsageToken(param.Value));
189                     separator = ";";
190                 }
191                 return sb.ToString();
192             }
193         }
194
195         /// <summary>
196         /// returns the composed cache key
197         /// </summary>
198         /// <returns></returns>
199         public override string ToString()
200         {
201             return String.Join("|", new string[] { Enum.GetName(typeof(CommandType), _commandType), _eSqlStatement, _parametersToken });
202         }
203
204     }
205 }