1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 6 7 //------------------------------------------------------------------------------ 8 9 using System.Collections.Generic; 10 using System.Diagnostics; 11 using System.Data.Common; 12 using System.Threading; 13 using System.Threading.Tasks; 14 15 namespace System.Data.ProviderBase 16 { 17 internal abstract class DbConnectionFactory 18 { 19 private Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> _connectionPoolGroups; 20 private readonly List<DbConnectionPool> _poolsToRelease; 21 private readonly List<DbConnectionPoolGroup> _poolGroupsToRelease; 22 private readonly Timer _pruningTimer; 23 private const int PruningDueTime = 4 * 60 * 1000; // 4 minutes 24 private const int PruningPeriod = 30 * 1000; // thirty seconds 25 26 27 // s_pendingOpenNonPooled is an array of tasks used to throttle creation of non-pooled connections to 28 // a maximum of Environment.ProcessorCount at a time. 29 private static uint s_pendingOpenNonPooledNext = 0; 30 private static Task<DbConnectionInternal>[] s_pendingOpenNonPooled = new Task<DbConnectionInternal>[Environment.ProcessorCount]; 31 private static Task<DbConnectionInternal> s_completedTask; 32 DbConnectionFactory()33 protected DbConnectionFactory() 34 { 35 _connectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>(); 36 _poolsToRelease = new List<DbConnectionPool>(); 37 _poolGroupsToRelease = new List<DbConnectionPoolGroup>(); 38 _pruningTimer = CreatePruningTimer(); 39 } 40 41 42 abstract public DbProviderFactory ProviderFactory 43 { 44 get; 45 } 46 47 ClearAllPools()48 public void ClearAllPools() 49 { 50 Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups; 51 foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> entry in connectionPoolGroups) 52 { 53 DbConnectionPoolGroup poolGroup = entry.Value; 54 if (null != poolGroup) 55 { 56 poolGroup.Clear(); 57 } 58 } 59 } 60 ClearPool(DbConnection connection)61 public void ClearPool(DbConnection connection) 62 { 63 ADP.CheckArgumentNull(connection, nameof(connection)); 64 65 DbConnectionPoolGroup poolGroup = GetConnectionPoolGroup(connection); 66 if (null != poolGroup) 67 { 68 poolGroup.Clear(); 69 } 70 } 71 ClearPool(DbConnectionPoolKey key)72 public void ClearPool(DbConnectionPoolKey key) 73 { 74 Debug.Assert(key != null, "key cannot be null"); 75 ADP.CheckArgumentNull(key.ConnectionString, nameof(key) + "." + nameof(key.ConnectionString)); 76 77 DbConnectionPoolGroup poolGroup; 78 Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups; 79 if (connectionPoolGroups.TryGetValue(key, out poolGroup)) 80 { 81 poolGroup.Clear(); 82 } 83 } 84 CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions)85 internal virtual DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions) 86 { 87 return null; 88 } 89 90 CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, DbConnectionOptions userOptions)91 internal DbConnectionInternal CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, DbConnectionOptions userOptions) 92 { 93 Debug.Assert(null != owningConnection, "null owningConnection?"); 94 Debug.Assert(null != poolGroup, "null poolGroup?"); 95 96 DbConnectionOptions connectionOptions = poolGroup.ConnectionOptions; 97 DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = poolGroup.ProviderInfo; 98 DbConnectionPoolKey poolKey = poolGroup.PoolKey; 99 100 DbConnectionInternal newConnection = CreateConnection(connectionOptions, poolKey, poolGroupProviderInfo, null, owningConnection, userOptions); 101 if (null != newConnection) 102 { 103 newConnection.MakeNonPooledObject(owningConnection); 104 } 105 return newConnection; 106 } 107 CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)108 internal DbConnectionInternal CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) 109 { 110 Debug.Assert(null != pool, "null pool?"); 111 DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = pool.PoolGroup.ProviderInfo; 112 113 DbConnectionInternal newConnection = CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningObject, userOptions); 114 if (null != newConnection) 115 { 116 newConnection.MakePooledConnection(pool); 117 } 118 return newConnection; 119 } 120 CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions)121 virtual internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions) 122 { 123 return null; 124 } 125 CreatePruningTimer()126 private Timer CreatePruningTimer() 127 { 128 TimerCallback callback = new TimerCallback(PruneConnectionPoolGroups); 129 return new Timer(callback, null, PruningDueTime, PruningPeriod); 130 } 131 FindConnectionOptions(DbConnectionPoolKey key)132 protected DbConnectionOptions FindConnectionOptions(DbConnectionPoolKey key) 133 { 134 Debug.Assert(key != null, "key cannot be null"); 135 if (!string.IsNullOrEmpty(key.ConnectionString)) 136 { 137 DbConnectionPoolGroup connectionPoolGroup; 138 Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups; 139 if (connectionPoolGroups.TryGetValue(key, out connectionPoolGroup)) 140 { 141 return connectionPoolGroup.ConnectionOptions; 142 } 143 } 144 return null; 145 } 146 GetCompletedTask()147 private static Task<DbConnectionInternal> GetCompletedTask() 148 { 149 Debug.Assert(Monitor.IsEntered(s_pendingOpenNonPooled), $"Expected {nameof(s_pendingOpenNonPooled)} lock to be held."); 150 return s_completedTask ?? (s_completedTask = Task.FromResult<DbConnectionInternal>(null)); 151 } 152 TryGetConnection(DbConnection owningConnection, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection)153 internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection) 154 { 155 Debug.Assert(null != owningConnection, "null owningConnection?"); 156 157 DbConnectionPoolGroup poolGroup; 158 DbConnectionPool connectionPool; 159 connection = null; 160 161 // Work around race condition with clearing the pool between GetConnectionPool obtaining pool 162 // and GetConnection on the pool checking the pool state. Clearing the pool in this window 163 // will switch the pool into the ShuttingDown state, and GetConnection will return null. 164 // There is probably a better solution involving locking the pool/group, but that entails a major 165 // re-design of the connection pooling synchronization, so is postponed for now. 166 167 // Use retriesLeft to prevent CPU spikes with incremental sleep 168 // start with one msec, double the time every retry 169 // max time is: 1 + 2 + 4 + ... + 2^(retries-1) == 2^retries -1 == 1023ms (for 10 retries) 170 int retriesLeft = 10; 171 int timeBetweenRetriesMilliseconds = 1; 172 173 do 174 { 175 poolGroup = GetConnectionPoolGroup(owningConnection); 176 // Doing this on the callers thread is important because it looks up the WindowsIdentity from the thread. 177 connectionPool = GetConnectionPool(owningConnection, poolGroup); 178 if (null == connectionPool) 179 { 180 // If GetConnectionPool returns null, we can be certain that 181 // this connection should not be pooled via DbConnectionPool 182 // or have a disabled pool entry. 183 poolGroup = GetConnectionPoolGroup(owningConnection); // previous entry have been disabled 184 185 if (retry != null) 186 { 187 Task<DbConnectionInternal> newTask; 188 CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); 189 lock (s_pendingOpenNonPooled) 190 { 191 // look for an available task slot (completed or empty) 192 int idx; 193 for (idx = 0; idx < s_pendingOpenNonPooled.Length; idx++) 194 { 195 Task task = s_pendingOpenNonPooled[idx]; 196 if (task == null) 197 { 198 s_pendingOpenNonPooled[idx] = GetCompletedTask(); 199 break; 200 } 201 else if (task.IsCompleted) 202 { 203 break; 204 } 205 } 206 207 // if didn't find one, pick the next one in round-robin fashion 208 if (idx == s_pendingOpenNonPooled.Length) 209 { 210 idx = (int)(s_pendingOpenNonPooledNext % s_pendingOpenNonPooled.Length); 211 unchecked 212 { 213 s_pendingOpenNonPooledNext++; 214 } 215 } 216 217 // now that we have an antecedent task, schedule our work when it is completed. 218 // If it is a new slot or a completed task, this continuation will start right away. 219 newTask = s_pendingOpenNonPooled[idx].ContinueWith((_) => 220 { 221 Transactions.Transaction originalTransaction = ADP.GetCurrentTransaction(); 222 try 223 { 224 ADP.SetCurrentTransaction(retry.Task.AsyncState as Transactions.Transaction); 225 var newConnection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions); 226 if ((oldConnection != null) && (oldConnection.State == ConnectionState.Open)) 227 { 228 oldConnection.PrepareForReplaceConnection(); 229 oldConnection.Dispose(); 230 } 231 return newConnection; 232 } 233 finally 234 { 235 ADP.SetCurrentTransaction(originalTransaction); 236 } 237 }, cancellationTokenSource.Token, TaskContinuationOptions.LongRunning, TaskScheduler.Default); 238 239 // Place this new task in the slot so any future work will be queued behind it 240 s_pendingOpenNonPooled[idx] = newTask; 241 } 242 243 // Set up the timeout (if needed) 244 if (owningConnection.ConnectionTimeout > 0) 245 { 246 int connectionTimeoutMilliseconds = owningConnection.ConnectionTimeout * 1000; 247 cancellationTokenSource.CancelAfter(connectionTimeoutMilliseconds); 248 } 249 250 // once the task is done, propagate the final results to the original caller 251 newTask.ContinueWith((task) => 252 { 253 cancellationTokenSource.Dispose(); 254 if (task.IsCanceled) 255 { 256 retry.TrySetException(ADP.ExceptionWithStackTrace(ADP.NonPooledOpenTimeout())); 257 } 258 else if (task.IsFaulted) 259 { 260 retry.TrySetException(task.Exception.InnerException); 261 } 262 else 263 { 264 if (!retry.TrySetResult(task.Result)) 265 { 266 // The outer TaskCompletionSource was already completed 267 // Which means that we don't know if someone has messed with the outer connection in the middle of creation 268 // So the best thing to do now is to destroy the newly created connection 269 task.Result.DoomThisConnection(); 270 task.Result.Dispose(); 271 } 272 } 273 }, TaskScheduler.Default); 274 275 return false; 276 } 277 278 connection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions); 279 } 280 else 281 { 282 if (((SqlClient.SqlConnection)owningConnection).ForceNewConnection) 283 { 284 Debug.Assert(!(oldConnection is DbConnectionClosed), "Force new connection, but there is no old connection"); 285 connection = connectionPool.ReplaceConnection(owningConnection, userOptions, oldConnection); 286 } 287 else 288 { 289 if (!connectionPool.TryGetConnection(owningConnection, retry, userOptions, out connection)) 290 { 291 return false; 292 } 293 } 294 295 if (connection == null) 296 { 297 // connection creation failed on semaphore waiting or if max pool reached 298 if (connectionPool.IsRunning) 299 { 300 // If GetConnection failed while the pool is running, the pool timeout occurred. 301 throw ADP.PooledOpenTimeout(); 302 } 303 else 304 { 305 // We've hit the race condition, where the pool was shut down after we got it from the group. 306 // Yield time slice to allow shut down activities to complete and a new, running pool to be instantiated 307 // before retrying. 308 Threading.Thread.Sleep(timeBetweenRetriesMilliseconds); 309 timeBetweenRetriesMilliseconds *= 2; // double the wait time for next iteration 310 } 311 } 312 } 313 } while (connection == null && retriesLeft-- > 0); 314 315 if (connection == null) 316 { 317 // exhausted all retries or timed out - give up 318 throw ADP.PooledOpenTimeout(); 319 } 320 321 return true; 322 } 323 GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup)324 private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup) 325 { 326 // if poolgroup is disabled, it will be replaced with a new entry 327 328 Debug.Assert(null != owningObject, "null owningObject?"); 329 Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?"); 330 331 // It is possible that while the outer connection object has 332 // been sitting around in a closed and unused state in some long 333 // running app, the pruner may have come along and remove this 334 // the pool entry from the master list. If we were to use a 335 // pool entry in this state, we would create "unmanaged" pools, 336 // which would be bad. To avoid this problem, we automagically 337 // re-create the pool entry whenever it's disabled. 338 339 // however, don't rebuild connectionOptions if no pooling is involved - let new connections do that work 340 if (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions)) 341 { 342 // reusing existing pool option in case user originally used SetConnectionPoolOptions 343 DbConnectionPoolGroupOptions poolOptions = connectionPoolGroup.PoolGroupOptions; 344 345 // get the string to hash on again 346 DbConnectionOptions connectionOptions = connectionPoolGroup.ConnectionOptions; 347 Debug.Assert(null != connectionOptions, "prevent expansion of connectionString"); 348 349 connectionPoolGroup = GetConnectionPoolGroup(connectionPoolGroup.PoolKey, poolOptions, ref connectionOptions); 350 Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?"); 351 SetConnectionPoolGroup(owningObject, connectionPoolGroup); 352 } 353 DbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this); 354 return connectionPool; 355 } 356 GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, ref DbConnectionOptions userConnectionOptions)357 internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, ref DbConnectionOptions userConnectionOptions) 358 { 359 if (string.IsNullOrEmpty(key.ConnectionString)) 360 { 361 return (DbConnectionPoolGroup)null; 362 } 363 364 DbConnectionPoolGroup connectionPoolGroup; 365 Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups; 366 if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup) || (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions))) 367 { 368 // If we can't find an entry for the connection string in 369 // our collection of pool entries, then we need to create a 370 // new pool entry and add it to our collection. 371 372 DbConnectionOptions connectionOptions = CreateConnectionOptions(key.ConnectionString, userConnectionOptions); 373 if (null == connectionOptions) 374 { 375 throw ADP.InternalConnectionError(ADP.ConnectionError.ConnectionOptionsMissing); 376 } 377 378 if (null == userConnectionOptions) 379 { // we only allow one expansion on the connection string 380 userConnectionOptions = connectionOptions; 381 } 382 383 // We don't support connection pooling on Win9x 384 if (null == poolOptions) 385 { 386 if (null != connectionPoolGroup) 387 { 388 // reusing existing pool option in case user originally used SetConnectionPoolOptions 389 poolOptions = connectionPoolGroup.PoolGroupOptions; 390 } 391 else 392 { 393 // Note: may return null for non-pooled connections 394 poolOptions = CreateConnectionPoolGroupOptions(connectionOptions); 395 } 396 } 397 398 lock (this) 399 { 400 connectionPoolGroups = _connectionPoolGroups; 401 if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup)) 402 { 403 DbConnectionPoolGroup newConnectionPoolGroup = new DbConnectionPoolGroup(connectionOptions, key, poolOptions); 404 newConnectionPoolGroup.ProviderInfo = CreateConnectionPoolGroupProviderInfo(connectionOptions); 405 406 // build new dictionary with space for new connection string 407 Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> newConnectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>(1 + connectionPoolGroups.Count); 408 foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> entry in connectionPoolGroups) 409 { 410 newConnectionPoolGroups.Add(entry.Key, entry.Value); 411 } 412 413 // lock prevents race condition with PruneConnectionPoolGroups 414 newConnectionPoolGroups.Add(key, newConnectionPoolGroup); 415 connectionPoolGroup = newConnectionPoolGroup; 416 _connectionPoolGroups = newConnectionPoolGroups; 417 } 418 else 419 { 420 Debug.Assert(!connectionPoolGroup.IsDisabled, "Disabled pool entry discovered"); 421 } 422 } 423 Debug.Assert(null != connectionPoolGroup, "how did we not create a pool entry?"); 424 Debug.Assert(null != userConnectionOptions, "how did we not have user connection options?"); 425 } 426 else if (null == userConnectionOptions) 427 { 428 userConnectionOptions = connectionPoolGroup.ConnectionOptions; 429 } 430 return connectionPoolGroup; 431 } 432 433 PruneConnectionPoolGroups(object state)434 private void PruneConnectionPoolGroups(object state) 435 { 436 // First, walk the pool release list and attempt to clear each 437 // pool, when the pool is finally empty, we dispose of it. If the 438 // pool isn't empty, it's because there are active connections or 439 // distributed transactions that need it. 440 lock (_poolsToRelease) 441 { 442 if (0 != _poolsToRelease.Count) 443 { 444 DbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray(); 445 foreach (DbConnectionPool pool in poolsToRelease) 446 { 447 if (null != pool) 448 { 449 pool.Clear(); 450 451 if (0 == pool.Count) 452 { 453 _poolsToRelease.Remove(pool); 454 } 455 } 456 } 457 } 458 } 459 460 // Next, walk the pool entry release list and dispose of each 461 // pool entry when it is finally empty. If the pool entry isn't 462 // empty, it's because there are active pools that need it. 463 lock (_poolGroupsToRelease) 464 { 465 if (0 != _poolGroupsToRelease.Count) 466 { 467 DbConnectionPoolGroup[] poolGroupsToRelease = _poolGroupsToRelease.ToArray(); 468 foreach (DbConnectionPoolGroup poolGroup in poolGroupsToRelease) 469 { 470 if (null != poolGroup) 471 { 472 int poolsLeft = poolGroup.Clear(); // may add entries to _poolsToRelease 473 474 if (0 == poolsLeft) 475 { 476 _poolGroupsToRelease.Remove(poolGroup); 477 } 478 } 479 } 480 } 481 } 482 483 // Finally, we walk through the collection of connection pool entries 484 // and prune each one. This will cause any empty pools to be put 485 // into the release list. 486 lock (this) 487 { 488 Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups; 489 Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> newConnectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>(connectionPoolGroups.Count); 490 491 foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> entry in connectionPoolGroups) 492 { 493 if (null != entry.Value) 494 { 495 Debug.Assert(!entry.Value.IsDisabled, "Disabled pool entry discovered"); 496 497 // entries start active and go idle during prune if all pools are gone 498 // move idle entries from last prune pass to a queue for pending release 499 // otherwise process entry which may move it from active to idle 500 if (entry.Value.Prune()) 501 { // may add entries to _poolsToRelease 502 QueuePoolGroupForRelease(entry.Value); 503 } 504 else 505 { 506 newConnectionPoolGroups.Add(entry.Key, entry.Value); 507 } 508 } 509 } 510 _connectionPoolGroups = newConnectionPoolGroups; 511 } 512 } 513 QueuePoolForRelease(DbConnectionPool pool, bool clearing)514 internal void QueuePoolForRelease(DbConnectionPool pool, bool clearing) 515 { 516 // Queue the pool up for release -- we'll clear it out and dispose 517 // of it as the last part of the pruning timer callback so we don't 518 // do it with the pool entry or the pool collection locked. 519 Debug.Assert(null != pool, "null pool?"); 520 521 // set the pool to the shutdown state to force all active 522 // connections to be automatically disposed when they 523 // are returned to the pool 524 pool.Shutdown(); 525 526 lock (_poolsToRelease) 527 { 528 if (clearing) 529 { 530 pool.Clear(); 531 } 532 _poolsToRelease.Add(pool); 533 } 534 } 535 QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup)536 internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) 537 { 538 Debug.Assert(null != poolGroup, "null poolGroup?"); 539 540 lock (_poolGroupsToRelease) 541 { 542 _poolGroupsToRelease.Add(poolGroup); 543 } 544 } 545 CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)546 virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) 547 { 548 return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection); 549 } 550 GetMetaDataFactory(DbConnectionPoolGroup connectionPoolGroup, DbConnectionInternal internalConnection)551 internal DbMetaDataFactory GetMetaDataFactory(DbConnectionPoolGroup connectionPoolGroup, DbConnectionInternal internalConnection) 552 { 553 Debug.Assert(connectionPoolGroup != null, "connectionPoolGroup may not be null."); 554 555 // get the matadatafactory from the pool entry. If it does not already have one 556 // create one and save it on the pool entry 557 DbMetaDataFactory metaDataFactory = connectionPoolGroup.MetaDataFactory; 558 559 // consider serializing this so we don't construct multiple metadata factories 560 // if two threads happen to hit this at the same time. One will be GC'd 561 if (metaDataFactory == null) 562 { 563 bool allowCache = false; 564 metaDataFactory = CreateMetaDataFactory(internalConnection, out allowCache); 565 if (allowCache) 566 { 567 connectionPoolGroup.MetaDataFactory = metaDataFactory; 568 } 569 } 570 return metaDataFactory; 571 } 572 CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory)573 protected virtual DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory) 574 { 575 // providers that support GetSchema must override this with a method that creates a meta data 576 // factory appropriate for them. 577 cacheMetaDataFactory = false; 578 throw ADP.NotSupported(); 579 } 580 CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection)581 abstract protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection); 582 CreateConnectionOptions(string connectionString, DbConnectionOptions previous)583 abstract protected DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous); 584 CreateConnectionPoolGroupOptions(DbConnectionOptions options)585 abstract protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions options); 586 GetConnectionPoolGroup(DbConnection connection)587 abstract internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection); 588 GetInnerConnection(DbConnection connection)589 abstract internal DbConnectionInternal GetInnerConnection(DbConnection connection); 590 591 PermissionDemand(DbConnection outerConnection)592 abstract internal void PermissionDemand(DbConnection outerConnection); 593 SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup)594 abstract internal void SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup); 595 SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to)596 abstract internal void SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to); 597 SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from)598 abstract internal bool SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from); 599 SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to)600 abstract internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to); 601 } 602 } 603