PageRenderTime 48ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/Enterprise Edition/EFCachingProvider/EFCachingCommand.cs

#
C# | 271 lines | 178 code | 31 blank | 62 comment | 17 complexity | 47cc97fa659df114152f1c8ede78bc80 MD5 | raw file
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Data;
  5. using System.Data.Common;
  6. using System.Data.Metadata.Edm;
  7. using System.Globalization;
  8. using System.Text;
  9. using System.Threading;
  10. using EFCachingProvider.Caching;
  11. using EFProviderWrapperToolkit;
  12. namespace EFCachingProvider
  13. {
  14. /// <summary>
  15. /// Implementation of <see cref="DbCommand"/> wrappr which implements query caching.
  16. /// </summary>
  17. public sealed class EFCachingCommand : DBCommandWrapper
  18. {
  19. private static int cacheableCommands;
  20. private static int nonCacheableCommands;
  21. private static int cacheHits;
  22. private static int cacheMisses;
  23. private static int cacheAdds;
  24. private EFCachingTransaction transaction;
  25. /// <summary>
  26. /// Initializes a new instance of the EFCachingCommand class.
  27. /// </summary>
  28. /// <param name="wrappedCommand">The wrapped command.</param>
  29. /// <param name="commandDefinition">The command definition.</param>
  30. public EFCachingCommand(DbCommand wrappedCommand, DBCommandDefinitionWrapper commandDefinition)
  31. : base(wrappedCommand, commandDefinition)
  32. {
  33. }
  34. /// <summary>
  35. /// Gets the number of cacheable commands.
  36. /// </summary>
  37. /// <value>The cacheable commands.</value>
  38. public static int CacheableCommands
  39. {
  40. get { return cacheableCommands; }
  41. }
  42. /// <summary>
  43. /// Gets the number of non-cacheable commands.
  44. /// </summary>
  45. /// <value>The non cacheable commands.</value>
  46. public static int NonCacheableCommands
  47. {
  48. get { return nonCacheableCommands; }
  49. }
  50. /// <summary>
  51. /// Gets the number of cache hits.
  52. /// </summary>
  53. /// <value>The cache hits.</value>
  54. public static int CacheHits
  55. {
  56. get { return cacheHits; }
  57. }
  58. /// <summary>
  59. /// Gets the total number of cache misses.
  60. /// </summary>
  61. public static int CacheMisses
  62. {
  63. get { return cacheMisses; }
  64. }
  65. /// <summary>
  66. /// Gets the total number of cache adds.
  67. /// </summary>
  68. /// <value>The number of cache adds.</value>
  69. public static int CacheAdds
  70. {
  71. get { return cacheAdds; }
  72. }
  73. /// <summary>
  74. /// Gets or sets the <see cref="P:System.Data.Common.DbCommand.DbTransaction"/> within which this <see cref="T:System.Data.Common.DbCommand"/> object executes.
  75. /// </summary>
  76. /// <value></value>
  77. /// <returns>
  78. /// The transaction within which a Command object of a .NET Framework data provider executes. The default value is a null reference (Nothing in Visual Basic).
  79. /// </returns>
  80. protected override DbTransaction DbTransaction
  81. {
  82. get
  83. {
  84. return this.transaction;
  85. }
  86. set
  87. {
  88. this.transaction = (EFCachingTransaction)value;
  89. if (this.transaction != null)
  90. {
  91. WrappedCommand.Transaction = this.transaction.WrappedTransaction;
  92. }
  93. else
  94. {
  95. WrappedCommand.Transaction = null;
  96. }
  97. }
  98. }
  99. /// <summary>
  100. /// Gets <see cref="EFCachingConnection"/> used by this <see cref="T:System.Data.Common.DbCommand"/>.
  101. /// </summary>
  102. /// <returns>
  103. /// The connection to the data source.
  104. /// </returns>
  105. private new EFCachingConnection Connection
  106. {
  107. get { return (EFCachingConnection)base.Connection; }
  108. }
  109. private new EFCachingCommandDefinition Definition
  110. {
  111. get { return (EFCachingCommandDefinition)base.Definition; }
  112. }
  113. /// <summary>
  114. /// Executes a SQL statement against a connection object.
  115. /// </summary>
  116. /// <returns>The number of rows affected.</returns>
  117. public override int ExecuteNonQuery()
  118. {
  119. this.UpdateAffectedEntitySets();
  120. return WrappedCommand.ExecuteNonQuery();
  121. }
  122. /// <summary>
  123. /// Executes the query and returns the first column of the first row in the result set returned by the query. All other columns and rows are ignored.
  124. /// </summary>
  125. /// <returns>
  126. /// The first column of the first row in the result set.
  127. /// </returns>
  128. public override object ExecuteScalar()
  129. {
  130. this.UpdateAffectedEntitySets();
  131. return WrappedCommand.ExecuteScalar();
  132. }
  133. /// <summary>
  134. /// Executes the command text against the connection.
  135. /// </summary>
  136. /// <param name="behavior">An instance of <see cref="T:System.Data.CommandBehavior"/>.</param>
  137. /// <returns>
  138. /// A <see cref="T:System.Data.Common.DbDataReader"/>.
  139. /// </returns>
  140. protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
  141. {
  142. ICache cache = this.Connection.Cache;
  143. if (cache == null)
  144. {
  145. Interlocked.Increment(ref nonCacheableCommands);
  146. return WrappedCommand.ExecuteReader(behavior);
  147. }
  148. this.UpdateAffectedEntitySets();
  149. string cacheKey = this.GetCacheKey();
  150. if (cacheKey == null || !this.Definition.IsCacheable() || !this.Connection.CachingPolicy.CanBeCached(this.Definition))
  151. {
  152. // non-cacheable
  153. Interlocked.Increment(ref nonCacheableCommands);
  154. return WrappedCommand.ExecuteReader(behavior);
  155. }
  156. object value;
  157. Interlocked.Increment(ref cacheableCommands);
  158. if (cache.GetItem(cacheKey, out value))
  159. {
  160. Interlocked.Increment(ref cacheHits);
  161. // got cache entry - create reader based on that
  162. return new CachingDataReaderCacheReader((DBQueryResults)value);
  163. }
  164. else
  165. {
  166. Interlocked.Increment(ref cacheMisses);
  167. List<string> dependentEntitySets = new List<string>();
  168. foreach (EntitySetBase set in this.Definition.AffectedEntitySets)
  169. {
  170. dependentEntitySets.Add(set.Name);
  171. }
  172. int minCacheableRows, maxCacheableRows;
  173. this.Connection.CachingPolicy.GetCacheableRows(this.Definition, out minCacheableRows, out maxCacheableRows);
  174. return new EFCachingDataReaderCacheWriter(
  175. this.WrappedCommand.ExecuteReader(behavior),
  176. maxCacheableRows,
  177. delegate(DBQueryResults entry)
  178. {
  179. if (entry.Rows.Count >= minCacheableRows && entry.Rows.Count <= maxCacheableRows)
  180. {
  181. Interlocked.Increment(ref cacheAdds);
  182. DateTime absoluteExpiration;
  183. TimeSpan slidingExpiration;
  184. this.Connection.CachingPolicy.GetExpirationTimeout(this.Definition, out slidingExpiration, out absoluteExpiration);
  185. cache.PutItem(cacheKey, entry, dependentEntitySets, slidingExpiration, absoluteExpiration);
  186. }
  187. });
  188. }
  189. }
  190. private static string GetLiteralValue(object value)
  191. {
  192. if (value is string)
  193. {
  194. return "'" + value.ToString().Replace("'", "''") + "'";
  195. }
  196. else
  197. {
  198. return Convert.ToString(value, CultureInfo.InvariantCulture);
  199. }
  200. }
  201. private string GetCacheKey()
  202. {
  203. StringBuilder sb = new StringBuilder();
  204. sb.Append(CommandType);
  205. sb.Append("|");
  206. sb.Append(CommandText);
  207. foreach (DbParameter parameter in Parameters)
  208. {
  209. if (parameter.Direction != ParameterDirection.Input)
  210. {
  211. // we don't cache queries with output parameters
  212. return null;
  213. }
  214. sb = sb.Replace("@" + parameter.ParameterName, GetLiteralValue(parameter.Value));
  215. }
  216. #if HASH_COMMANDS
  217. byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
  218. string hashString = Convert.ToBase64String(MD5.Create().ComputeHash(bytes));
  219. // Console.WriteLine("hashString: {0}", hashString);
  220. return hashString;
  221. #else
  222. return sb.ToString();
  223. #endif
  224. }
  225. private void UpdateAffectedEntitySets()
  226. {
  227. if (this.transaction != null)
  228. {
  229. if (this.Definition.IsModification)
  230. {
  231. this.transaction.HasModifications = true;
  232. }
  233. foreach (EntitySetBase entitySet in this.Definition.AffectedEntitySets)
  234. {
  235. this.transaction.AddAffectedEntitySet(entitySet);
  236. }
  237. }
  238. }
  239. }
  240. }