/Rhino.Etl.Core/Infrastructure/SqlCommandSet.cs

http://github.com/ayende/rhino-etl · C# · 200 lines · 110 code · 18 blank · 72 comment · 6 complexity · 9f676ef3ffc2aec4d61952f5f0385537 MD5 · raw file

  1. #region license
  2. // Copyright (c) 2005 - 2007 Ayende Rahien (ayende@ayende.com)
  3. // All rights reserved.
  4. //
  5. // Redistribution and use in source and binary forms, with or without modification,
  6. // are permitted provided that the following conditions are met:
  7. //
  8. // * Redistributions of source code must retain the above copyright notice,
  9. // this list of conditions and the following disclaimer.
  10. // * Redistributions in binary form must reproduce the above copyright notice,
  11. // this list of conditions and the following disclaimer in the documentation
  12. // and/or other materials provided with the distribution.
  13. // * Neither the name of Ayende Rahien nor the names of its
  14. // contributors may be used to endorse or promote products derived from this
  15. // software without specific prior written permission.
  16. //
  17. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  18. // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19. // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20. // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
  21. // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  22. // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  23. // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  24. // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  25. // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  26. // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. #endregion
  28. using System;
  29. using System.Data.SqlClient;
  30. using System.Diagnostics;
  31. using System.Reflection;
  32. namespace Rhino.Etl.Core.Infrastructure
  33. {
  34. /// <summary>
  35. /// Expose the batch functionality in ADO.Net 2.0
  36. /// Microsoft in its wisdom decided to make my life hard and mark it internal.
  37. /// Through the use of Reflection and some delegates magic, I opened up the functionality.
  38. ///
  39. /// There is NO documentation for this, and likely zero support.
  40. /// Use at your own risk, etc...
  41. ///
  42. /// Observable performance benefits are 50%+ when used, so it is really worth it.
  43. /// </summary>
  44. public class SqlCommandSet : IDisposable
  45. {
  46. private static readonly Type sqlCmdSetType;
  47. private readonly object instance;
  48. private readonly PropSetter<SqlConnection> connectionSetter;
  49. private readonly PropSetter<SqlTransaction> transactionSetter;
  50. private readonly PropSetter<int> timeoutSetter;
  51. private readonly PropGetter<SqlConnection> connectionGetter;
  52. private readonly PropGetter<SqlCommand> commandGetter;
  53. private readonly AppendCommand doAppend;
  54. private readonly ExecuteNonQueryCommand doExecuteNonQuery;
  55. private readonly DisposeCommand doDispose;
  56. private int countOfCommands = 0;
  57. static SqlCommandSet()
  58. {
  59. Assembly sysData = Assembly.Load("System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
  60. sqlCmdSetType = sysData.GetType("System.Data.SqlClient.SqlCommandSet");
  61. Guard.Against(sqlCmdSetType == null, "Could not find SqlCommandSet!");
  62. }
  63. /// <summary>
  64. /// Creates a new instance of SqlCommandSet
  65. /// </summary>
  66. public SqlCommandSet()
  67. {
  68. instance = Activator.CreateInstance(sqlCmdSetType, true);
  69. timeoutSetter = (PropSetter<int>)
  70. Delegate.CreateDelegate(typeof(PropSetter<int>),
  71. instance, "set_CommandTimeout");
  72. connectionSetter = (PropSetter<SqlConnection>)
  73. Delegate.CreateDelegate(typeof(PropSetter<SqlConnection>),
  74. instance, "set_Connection");
  75. transactionSetter = (PropSetter<SqlTransaction>)
  76. Delegate.CreateDelegate(typeof(PropSetter<SqlTransaction>),
  77. instance, "set_Transaction");
  78. connectionGetter = (PropGetter<SqlConnection>)
  79. Delegate.CreateDelegate(typeof(PropGetter<SqlConnection>),
  80. instance, "get_Connection");
  81. commandGetter =
  82. (PropGetter<SqlCommand>)Delegate.CreateDelegate(typeof(PropGetter<SqlCommand>), instance, "get_BatchCommand");
  83. doAppend = (AppendCommand)Delegate.CreateDelegate(typeof(AppendCommand), instance, "Append");
  84. doExecuteNonQuery = (ExecuteNonQueryCommand)
  85. Delegate.CreateDelegate(typeof(ExecuteNonQueryCommand),
  86. instance, "ExecuteNonQuery");
  87. doDispose = (DisposeCommand)Delegate.CreateDelegate(typeof(DisposeCommand), instance, "Dispose");
  88. }
  89. /// <summary>
  90. /// Append a command to the batch
  91. /// </summary>
  92. /// <param name="command"></param>
  93. public void Append(SqlCommand command)
  94. {
  95. AssertHasParameters(command);
  96. doAppend(command);
  97. countOfCommands++;
  98. }
  99. /// <summary>
  100. /// This is required because SqlClient.SqlCommandSet will throw if
  101. /// the command has no parameters.
  102. /// </summary>
  103. /// <param name="command"></param>
  104. private static void AssertHasParameters(SqlCommand command)
  105. {
  106. if (command.Parameters.Count == 0 &&
  107. (RuntimeInfo.Version.Contains("2.0") || RuntimeInfo.Version.Contains("1.1")))
  108. {
  109. throw new ArgumentException(
  110. "A command in SqlCommandSet must have parameters. You can't pass hardcoded sql strings.");
  111. }
  112. }
  113. /// <summary>
  114. /// Return the batch command to be executed
  115. /// </summary>
  116. public SqlCommand BatchCommand
  117. {
  118. get
  119. {
  120. return commandGetter();
  121. }
  122. }
  123. /// <summary>
  124. /// The number of commands batched in this instance
  125. /// </summary>
  126. public int CountOfCommands
  127. {
  128. get { return countOfCommands; }
  129. }
  130. /// <summary>
  131. /// Executes the batch
  132. /// </summary>
  133. /// <returns>
  134. /// This seems to be returning the total number of affected rows in all queries
  135. /// </returns>
  136. public int ExecuteNonQuery()
  137. {
  138. Guard.Against<ArgumentException>(Connection == null,
  139. "Connection was not set! You must set the connection property before calling ExecuteNonQuery()");
  140. if(CountOfCommands==0)
  141. return 0;
  142. return doExecuteNonQuery();
  143. }
  144. ///<summary>
  145. /// The connection the batch will use
  146. ///</summary>
  147. public SqlConnection Connection
  148. {
  149. get { return connectionGetter(); }
  150. set { connectionSetter(value); }
  151. }
  152. /// <summary>
  153. /// Set the timeout of the commandSet
  154. /// </summary>
  155. public int CommandTimeout
  156. {
  157. set { timeoutSetter(value); }
  158. }
  159. /// <summary>
  160. /// The transaction the batch will run as part of
  161. /// </summary>
  162. public SqlTransaction Transaction
  163. {
  164. set { transactionSetter(value); }
  165. }
  166. ///<summary>
  167. ///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  168. ///</summary>
  169. ///<filterpriority>2</filterpriority>
  170. public void Dispose()
  171. {
  172. doDispose();
  173. }
  174. #region Delegate Definations
  175. private delegate void PropSetter<T>(T item);
  176. private delegate T PropGetter<T>();
  177. private delegate void AppendCommand(SqlCommand command);
  178. private delegate int ExecuteNonQueryCommand();
  179. private delegate void DisposeCommand();
  180. #endregion
  181. }
  182. }