DbConnectionHelper.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. //------------------------------------------------------------------------------
  2. // <copyright file="DbConnectionHelper.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">[....]</owner>
  6. //------------------------------------------------------------------------------
  7. namespace NAMESPACE {
  8. using System;
  9. using System.ComponentModel;
  10. using System.Data;
  11. using System.Data.Common;
  12. using System.Data.ProviderBase;
  13. using System.Diagnostics;
  14. using System.Globalization;
  15. using System.Runtime.ConstrainedExecution;
  16. using System.Runtime.InteropServices;
  17. using System.Threading;
  18. using SysTx = System.Transactions;
  19. using System.Diagnostics.CodeAnalysis;
  20. public sealed partial class CONNECTIONOBJECTNAME : DbConnection {
  21. private static readonly DbConnectionFactory _connectionFactory = CONNECTIONFACTORYOBJECTNAME;
  22. internal static readonly System.Security.CodeAccessPermission ExecutePermission = CONNECTIONOBJECTNAME.CreateExecutePermission();
  23. private DbConnectionOptions _userConnectionOptions;
  24. private DbConnectionPoolGroup _poolGroup;
  25. private DbConnectionInternal _innerConnection;
  26. private int _closeCount; // used to distinguish between different uses of this object, so we don't have to maintain a list of it's children
  27. private static int _objectTypeCount; // Bid counter
  28. internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
  29. public CONNECTIONOBJECTNAME() : base() {
  30. GC.SuppressFinalize(this);
  31. _innerConnection = DbConnectionClosedNeverOpened.SingletonInstance;
  32. }
  33. // Copy Constructor
  34. private void CopyFrom(CONNECTIONOBJECTNAME connection) { // V1.2.3300
  35. ADP.CheckArgumentNull(connection, "connection");
  36. _userConnectionOptions = connection.UserConnectionOptions;
  37. _poolGroup = connection.PoolGroup;
  38. // SQLBU 432115
  39. // Match the original connection's behavior for whether the connection was never opened,
  40. // but ensure Clone is in the closed state.
  41. if (DbConnectionClosedNeverOpened.SingletonInstance == connection._innerConnection)
  42. {
  43. _innerConnection = DbConnectionClosedNeverOpened.SingletonInstance;
  44. }
  45. else
  46. {
  47. _innerConnection = DbConnectionClosedPreviouslyOpened.SingletonInstance;
  48. }
  49. }
  50. /// <devdoc>We use the _closeCount to avoid having to know about all our
  51. /// children; instead of keeping a collection of all the objects that
  52. /// would be affected by a close, we simply increment the _closeCount
  53. /// and have each of our children check to see if they're "orphaned"
  54. /// </devdoc>
  55. internal int CloseCount {
  56. get {
  57. return _closeCount;
  58. }
  59. }
  60. internal DbConnectionFactory ConnectionFactory {
  61. get {
  62. return _connectionFactory;
  63. }
  64. }
  65. internal DbConnectionOptions ConnectionOptions {
  66. get {
  67. System.Data.ProviderBase.DbConnectionPoolGroup poolGroup = PoolGroup;
  68. return ((null != poolGroup) ? poolGroup.ConnectionOptions : null);
  69. }
  70. }
  71. private string ConnectionString_Get() {
  72. Bid.Trace( "<prov.DbConnectionHelper.ConnectionString_Get|API> %d#\n", ObjectID);
  73. bool hidePassword = InnerConnection.ShouldHidePassword;
  74. DbConnectionOptions connectionOptions = UserConnectionOptions;
  75. return ((null != connectionOptions) ? connectionOptions.UsersConnectionString(hidePassword) : "");
  76. }
  77. private void ConnectionString_Set(string value) {
  78. DbConnectionPoolKey key = new DbConnectionPoolKey(value);
  79. ConnectionString_Set(key);
  80. }
  81. private void ConnectionString_Set(DbConnectionPoolKey key) {
  82. DbConnectionOptions connectionOptions = null;
  83. System.Data.ProviderBase.DbConnectionPoolGroup poolGroup = ConnectionFactory.GetConnectionPoolGroup(key, null, ref connectionOptions);
  84. DbConnectionInternal connectionInternal = InnerConnection;
  85. bool flag = connectionInternal.AllowSetConnectionString;
  86. if (flag) {
  87. //try {
  88. // NOTE: There's a race condition with multiple threads changing
  89. // ConnectionString and any thread throws an exception
  90. // Closed->Busy: prevent Open during set_ConnectionString
  91. flag = SetInnerConnectionFrom(DbConnectionClosedBusy.SingletonInstance, connectionInternal);
  92. if (flag) {
  93. _userConnectionOptions = connectionOptions;
  94. _poolGroup = poolGroup;
  95. _innerConnection = DbConnectionClosedNeverOpened.SingletonInstance;
  96. }
  97. //}
  98. //catch {
  99. // // recover from exceptions to avoid sticking in busy state
  100. // SetInnerConnectionFrom(connectionInternal, DbConnectionClosedBusy.SingletonInstance);
  101. // throw;
  102. //}
  103. }
  104. if (!flag) {
  105. throw ADP.OpenConnectionPropertySet(ADP.ConnectionString, connectionInternal.State);
  106. }
  107. if (Bid.TraceOn) {
  108. string cstr = ((null != connectionOptions) ? connectionOptions.UsersConnectionStringForTrace() : "");
  109. Bid.Trace("<prov.DbConnectionHelper.ConnectionString_Set|API> %d#, '%ls'\n", ObjectID, cstr);
  110. }
  111. }
  112. internal DbConnectionInternal InnerConnection {
  113. get {
  114. return _innerConnection;
  115. }
  116. }
  117. internal System.Data.ProviderBase.DbConnectionPoolGroup PoolGroup {
  118. get {
  119. return _poolGroup;
  120. }
  121. set {
  122. // when a poolgroup expires and the connection eventually activates, the pool entry will be replaced
  123. Debug.Assert(null != value, "null poolGroup");
  124. _poolGroup = value;
  125. }
  126. }
  127. internal DbConnectionOptions UserConnectionOptions {
  128. get {
  129. return _userConnectionOptions;
  130. }
  131. }
  132. // Open->ClosedPreviouslyOpened, and doom the internal connection too...
  133. [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
  134. internal void Abort(Exception e) {
  135. DbConnectionInternal innerConnection = _innerConnection; // Should not cause memory allocation...
  136. if (ConnectionState.Open == innerConnection.State) {
  137. Interlocked.CompareExchange(ref _innerConnection, DbConnectionClosedPreviouslyOpened.SingletonInstance, innerConnection);
  138. innerConnection.DoomThisConnection();
  139. }
  140. // NOTE: we put the tracing last, because the ToString() calls (and
  141. // the Bid.Trace, for that matter) have no reliability contract and
  142. // will end the reliable try...
  143. if (e is OutOfMemoryException) {
  144. Bid.Trace("<prov.DbConnectionHelper.Abort|RES|INFO|CPOOL> %d#, Aborting operation due to asynchronous exception: %ls\n", ObjectID, "OutOfMemory");
  145. }
  146. else {
  147. Bid.Trace("<prov.DbConnectionHelper.Abort|RES|INFO|CPOOL> %d#, Aborting operation due to asynchronous exception: %ls\n", ObjectID, e.ToString());
  148. }
  149. }
  150. internal void AddWeakReference(object value, int tag) {
  151. InnerConnection.AddWeakReference(value, tag);
  152. }
  153. override protected DbCommand CreateDbCommand() {
  154. DbCommand command = null;
  155. IntPtr hscp;
  156. Bid.ScopeEnter(out hscp, "<prov.DbConnectionHelper.CreateDbCommand|API> %d#\n", ObjectID);
  157. try {
  158. DbProviderFactory providerFactory = ConnectionFactory.ProviderFactory;
  159. command = providerFactory.CreateCommand();
  160. command.Connection = this;
  161. }
  162. finally {
  163. Bid.ScopeLeave(ref hscp);
  164. }
  165. return command;
  166. }
  167. private static System.Security.CodeAccessPermission CreateExecutePermission() {
  168. DBDataPermission p = (DBDataPermission)CONNECTIONFACTORYOBJECTNAME.ProviderFactory.CreatePermission(System.Security.Permissions.PermissionState.None);
  169. p.Add(String.Empty, String.Empty, KeyRestrictionBehavior.AllowOnly);
  170. return p;
  171. }
  172. override protected void Dispose(bool disposing) {
  173. if (disposing) {
  174. _userConnectionOptions = null;
  175. _poolGroup= null;
  176. Close();
  177. }
  178. DisposeMe(disposing);
  179. base.Dispose(disposing); // notify base classes
  180. }
  181. partial void RepairInnerConnection();
  182. #if !MOBILE
  183. // NOTE: This is just a private helper because OracleClient V1.1 shipped
  184. // with a different argument name and it's a breaking change to not use
  185. // the same argument names in V2.0 (VB Named Parameter Binding--Ick)
  186. private void EnlistDistributedTransactionHelper(System.EnterpriseServices.ITransaction transaction) {
  187. System.Security.PermissionSet permissionSet = new System.Security.PermissionSet(System.Security.Permissions.PermissionState.None);
  188. permissionSet.AddPermission(CONNECTIONOBJECTNAME.ExecutePermission); // MDAC 81476
  189. permissionSet.AddPermission(new System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode));
  190. permissionSet.Demand();
  191. Bid.Trace( "<prov.DbConnectionHelper.EnlistDistributedTransactionHelper|RES|TRAN> %d#, Connection enlisting in a transaction.\n", ObjectID);
  192. SysTx.Transaction indigoTransaction = null;
  193. if (null != transaction) {
  194. indigoTransaction = SysTx.TransactionInterop.GetTransactionFromDtcTransaction((SysTx.IDtcTransaction)transaction);
  195. }
  196. RepairInnerConnection();
  197. // NOTE: since transaction enlistment involves round trips to the
  198. // server, we don't want to lock here, we'll handle the race conditions
  199. // elsewhere.
  200. InnerConnection.EnlistTransaction(indigoTransaction);
  201. // NOTE: If this outer connection were to be GC'd while we're
  202. // enlisting, the pooler would attempt to reclaim the inner connection
  203. // while we're attempting to enlist; not sure how likely that is but
  204. // we should consider a GC.KeepAlive(this) here.
  205. GC.KeepAlive(this);
  206. }
  207. #endif
  208. override public void EnlistTransaction(SysTx.Transaction transaction) {
  209. CONNECTIONOBJECTNAME.ExecutePermission.Demand();
  210. Bid.Trace( "<prov.DbConnectionHelper.EnlistTransaction|RES|TRAN> %d#, Connection enlisting in a transaction.\n", ObjectID);
  211. // If we're currently enlisted in a transaction and we were called
  212. // on the EnlistTransaction method (Whidbey) we're not allowed to
  213. // enlist in a different transaction.
  214. DbConnectionInternal innerConnection = InnerConnection;
  215. // NOTE: since transaction enlistment involves round trips to the
  216. // server, we don't want to lock here, we'll handle the race conditions
  217. // elsewhere.
  218. SysTx.Transaction enlistedTransaction = innerConnection.EnlistedTransaction;
  219. if (enlistedTransaction != null) {
  220. // Allow calling enlist if already enlisted (no-op)
  221. if (enlistedTransaction.Equals(transaction)) {
  222. return;
  223. }
  224. // Allow enlisting in a different transaction if the enlisted transaction has completed.
  225. if (enlistedTransaction.TransactionInformation.Status == SysTx.TransactionStatus.Active)
  226. {
  227. throw ADP.TransactionPresent();
  228. }
  229. }
  230. RepairInnerConnection();
  231. InnerConnection.EnlistTransaction(transaction);
  232. // NOTE: If this outer connection were to be GC'd while we're
  233. // enlisting, the pooler would attempt to reclaim the inner connection
  234. // while we're attempting to enlist; not sure how likely that is but
  235. // we should consider a GC.KeepAlive(this) here.
  236. GC.KeepAlive(this);
  237. }
  238. private DbMetaDataFactory GetMetaDataFactory(DbConnectionInternal internalConnection) {
  239. return ConnectionFactory.GetMetaDataFactory(_poolGroup, internalConnection);
  240. }
  241. internal DbMetaDataFactory GetMetaDataFactoryInternal(DbConnectionInternal internalConnection) {
  242. return GetMetaDataFactory(internalConnection);
  243. }
  244. override public DataTable GetSchema() {
  245. return this.GetSchema(DbMetaDataCollectionNames.MetaDataCollections, null);
  246. }
  247. override public DataTable GetSchema(string collectionName) {
  248. return this.GetSchema(collectionName, null);
  249. }
  250. override public DataTable GetSchema(string collectionName, string[] restrictionValues) {
  251. // NOTE: This is virtual because not all providers may choose to support
  252. // returning schema data
  253. CONNECTIONOBJECTNAME.ExecutePermission.Demand();
  254. return InnerConnection.GetSchema(ConnectionFactory, PoolGroup, this, collectionName, restrictionValues);
  255. }
  256. internal void NotifyWeakReference(int message) {
  257. InnerConnection.NotifyWeakReference(message);
  258. }
  259. internal void PermissionDemand() {
  260. Debug.Assert(DbConnectionClosedConnecting.SingletonInstance == _innerConnection, "not connecting");
  261. System.Data.ProviderBase.DbConnectionPoolGroup poolGroup = PoolGroup;
  262. DbConnectionOptions connectionOptions = ((null != poolGroup) ? poolGroup.ConnectionOptions : null);
  263. if ((null == connectionOptions) || connectionOptions.IsEmpty) {
  264. throw ADP.NoConnectionString();
  265. }
  266. DbConnectionOptions userConnectionOptions = UserConnectionOptions;
  267. Debug.Assert(null != userConnectionOptions, "null UserConnectionOptions");
  268. userConnectionOptions.DemandPermission();
  269. }
  270. internal void RemoveWeakReference(object value) {
  271. InnerConnection.RemoveWeakReference(value);
  272. }
  273. // OpenBusy->Closed (previously opened)
  274. // Connecting->Open
  275. internal void SetInnerConnectionEvent(DbConnectionInternal to) {
  276. // Set's the internal connection without verifying that it's a specific value
  277. Debug.Assert(null != _innerConnection, "null InnerConnection");
  278. Debug.Assert(null != to, "to null InnerConnection");
  279. ConnectionState originalState = _innerConnection.State & ConnectionState.Open;
  280. ConnectionState currentState = to.State & ConnectionState.Open;
  281. if ((originalState != currentState) && (ConnectionState.Closed == currentState)) {
  282. // Increment the close count whenever we switch to Closed
  283. unchecked { _closeCount++; }
  284. }
  285. _innerConnection = to;
  286. if (ConnectionState.Closed == originalState && ConnectionState.Open == currentState) {
  287. OnStateChange(DbConnectionInternal.StateChangeOpen);
  288. }
  289. else if (ConnectionState.Open == originalState && ConnectionState.Closed == currentState) {
  290. OnStateChange(DbConnectionInternal.StateChangeClosed);
  291. }
  292. else {
  293. Debug.Assert(false, "unexpected state switch");
  294. if (originalState != currentState) {
  295. OnStateChange(new StateChangeEventArgs(originalState, currentState));
  296. }
  297. }
  298. }
  299. // this method is used to securely change state with the resource being
  300. // the open connection protected by the connectionstring via a permission demand
  301. // Closed->Connecting: prevent set_ConnectionString during Open
  302. // Open->OpenBusy: guarantee internal connection is returned to correct pool
  303. // Closed->ClosedBusy: prevent Open during set_ConnectionString
  304. internal bool SetInnerConnectionFrom(DbConnectionInternal to, DbConnectionInternal from) {
  305. // Set's the internal connection, verifying that it's a specific value before doing so.
  306. Debug.Assert(null != _innerConnection, "null InnerConnection");
  307. Debug.Assert(null != from, "from null InnerConnection");
  308. Debug.Assert(null != to, "to null InnerConnection");
  309. bool result = (from == Interlocked.CompareExchange<DbConnectionInternal>(ref _innerConnection, to, from));
  310. return result;
  311. }
  312. // ClosedBusy->Closed (never opened)
  313. // Connecting->Closed (exception during open, return to previous closed state)
  314. internal void SetInnerConnectionTo(DbConnectionInternal to) {
  315. // Set's the internal connection without verifying that it's a specific value
  316. Debug.Assert(null != _innerConnection, "null InnerConnection");
  317. Debug.Assert(null != to, "to null InnerConnection");
  318. _innerConnection = to;
  319. }
  320. [ConditionalAttribute("DEBUG")]
  321. internal static void VerifyExecutePermission() {
  322. try {
  323. // use this to help validate this code path is only used after the following permission has been previously demanded in the current codepath
  324. CONNECTIONOBJECTNAME.ExecutePermission.Demand();
  325. }
  326. catch(System.Security.SecurityException) {
  327. System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath");
  328. throw;
  329. }
  330. }
  331. }
  332. }