// Copyright (c) 2004, 2021, Oracle and/or its affiliates. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License, version 2.0, as // published by the Free Software Foundation. // // This program is also distributed with certain software (including // but not limited to OpenSSL) that is licensed under separate terms, // as designated in a particular file or component or in included license // documentation. The authors of MySQL hereby grant you an // additional permission to link the program and your derivative works // with the separately licensed software that they have included with // MySQL. // // Without limiting anything contained in the foregoing, this file, // which is part of MySQL Connector/NET, is also subject to the // Universal FOSS Exception, version 1.0, a copy of which can be found at // http://oss.oracle.com/licenses/universal-foss-exception. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License, version 2.0, for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA using System; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Threading; using System.Threading.Tasks; using MySql.Data.Common; using IsolationLevel = System.Data.IsolationLevel; using MySql.Data.MySqlClient.Interceptors; using System.Linq; using System.Transactions; using MySql.Data.MySqlClient.Replication; using MySql.Data.Failover; using System.Collections.Generic; using System.Net; using Sog.Properties; #if NET452 using System.Drawing.Design; #endif namespace MySql.Data.MySqlClient { /// public sealed partial class MySqlConnection : DbConnection { internal ConnectionState connectionState; internal Driver driver; internal bool hasBeenOpen; private SchemaProvider _schemaProvider; private ExceptionInterceptor _exceptionInterceptor; internal CommandInterceptor commandInterceptor; private bool _isKillQueryConnection; private string _database; private int _commandTimeout; /// public event MySqlInfoMessageEventHandler InfoMessage; private static readonly Cache ConnectionStringCache = new Cache(0, 25); /// public MySqlConnection() { // TODO: add event data to StateChange docs this.Settings = new MySqlConnectionStringBuilder(); this._database = String.Empty; } /// public MySqlConnection(string connectionString) : this() { this.Settings.AnalyzeConnectionString(connectionString ?? string.Empty, false, false); this.ConnectionString = connectionString; } #region Destructor ~MySqlConnection() { this.Dispose(false); } #endregion #region Interal Methods & Properties internal PerformanceMonitor PerfMonitor { get; private set; } internal ProcedureCache ProcedureCache { get; private set; } internal MySqlConnectionStringBuilder Settings { get; private set; } internal MySqlDataReader Reader { get { return this.driver?.reader; } set { this.driver.reader = value; this.IsInUse = this.driver.reader != null; } } internal void OnInfoMessage(MySqlInfoMessageEventArgs args) { this.InfoMessage?.Invoke(this, args); } internal bool SoftClosed { get { return (this.State == ConnectionState.Closed) && this.driver != null && this.driver.currentTransaction != null; } } internal bool IsInUse { get; set; } /// /// Determines whether the connection is a clone of other connection. /// internal bool IsClone { get; set; } internal bool ParentHasbeenOpen { get; set; } #endregion #region Properties /// /// Returns the id of the server thread this connection is executing on /// [Browsable(false)] public int ServerThread => this.driver.ThreadID; /// /// Gets the name of the MySQL server to which to connect. /// [Browsable(true)] public override string DataSource => this.Settings.Server; /// [Browsable(true)] public override int ConnectionTimeout => (int)this.Settings.ConnectionTimeout; /// [Browsable(true)] public override string Database => this._database; /// /// Indicates if this connection should use compression when communicating with the server. /// [Browsable(false)] public bool UseCompression => this.Settings.UseCompression; /// [Browsable(false)] public override ConnectionState State => this.connectionState; /// [Browsable(false)] public override string ServerVersion => this.driver.Version.ToString(); /// #if NET452 [Editor("MySql.Data.MySqlClient.Design.ConnectionStringTypeEditor,MySqlClient.Design", typeof(UITypeEditor))] #endif [Browsable(true)] [Category("Data")] [Description("Information used to connect to a DataSource, such as 'Server=xxx;UserId=yyy;Password=zzz;Database=dbdb'.")] public override string ConnectionString { get { // Always return exactly what the user set. // Security-sensitive information may be removed. return this.Settings.GetConnectionString(!this.IsClone ? (!this.hasBeenOpen || this.Settings.PersistSecurityInfo) : !this.Settings.PersistSecurityInfo ? (this.ParentHasbeenOpen ? false : !this.hasBeenOpen) : (this.Settings.PersistSecurityInfo)); } set { if (this.State != ConnectionState.Closed) { this.Throw(new MySqlException( "Not allowed to change the 'ConnectionString' property while the connection (state=" + this.State + ").")); } MySqlConnectionStringBuilder newSettings; lock (ConnectionStringCache) { if (value == null) { newSettings = new MySqlConnectionStringBuilder(); } else { newSettings = ConnectionStringCache[value]; if (null == newSettings || FailoverManager.FailoverGroup == null) { newSettings = new MySqlConnectionStringBuilder(value); ConnectionStringCache.Add(value, newSettings); } } } this.Settings = newSettings; if (!string.IsNullOrEmpty(this.Settings.Database)) { this._database = this.Settings.Database; } if (this.driver != null) { this.driver.Settings = newSettings; } } } protected override DbProviderFactory DbProviderFactory => MySqlClientFactory.Instance; /// /// Gets a boolean value that indicates whether the password associated to the connection is expired. /// public bool IsPasswordExpired => this.driver.IsPasswordExpired; #endregion protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) { if (isolationLevel == IsolationLevel.Unspecified) { return this.BeginTransaction(); } return this.BeginTransaction(isolationLevel); } protected override DbCommand CreateDbCommand() { return this.CreateCommand(); } #region IDisposeable protected override void Dispose(bool disposing) { if (this.State == ConnectionState.Open) { this.Close(); } base.Dispose(disposing); } #endregion #region Transactions /// public new MySqlTransaction BeginTransaction() { return this.BeginTransaction(IsolationLevel.RepeatableRead); } /// public MySqlTransaction BeginTransaction(IsolationLevel iso, string scope = "") { // TODO: check note in help if (this.State != ConnectionState.Open) { this.Throw(new InvalidOperationException(Resources.ConnectionNotOpen)); } // First check to see if we are in a current transaction if (this.driver.HasStatus(ServerStatusFlags.InTransaction)) { this.Throw(new InvalidOperationException(Resources.NoNestedTransactions)); } MySqlTransaction t = new MySqlTransaction(this, iso); MySqlCommand cmd = new MySqlCommand("", this); cmd.CommandText = $"SET {scope} TRANSACTION ISOLATION LEVEL "; switch (iso) { case IsolationLevel.ReadCommitted: cmd.CommandText += "READ COMMITTED"; break; case IsolationLevel.ReadUncommitted: cmd.CommandText += "READ UNCOMMITTED"; break; case IsolationLevel.RepeatableRead: cmd.CommandText += "REPEATABLE READ"; break; case IsolationLevel.Serializable: cmd.CommandText += "SERIALIZABLE"; break; case IsolationLevel.Chaos: this.Throw(new NotSupportedException(Resources.ChaosNotSupported)); break; case IsolationLevel.Snapshot: this.Throw(new NotSupportedException(Resources.SnapshotNotSupported)); break; } cmd.ExecuteNonQuery(); cmd.CommandText = "BEGIN"; cmd.CommandType = CommandType.Text; cmd.ExecuteNonQuery(); return t; } #endregion /// public override void ChangeDatabase(string databaseName) { if (databaseName == null || databaseName.Trim().Length == 0) { this.Throw(new ArgumentException(Resources.ParameterIsInvalid, "databaseName")); } if (this.State != ConnectionState.Open) { this.Throw(new InvalidOperationException(Resources.ConnectionNotOpen)); } // This lock prevents promotable transaction rollback to run // in parallel lock (this.driver) { // We use default command timeout for SetDatabase using (new CommandTimer(this, (int)this.Settings.DefaultCommandTimeout)) { this.driver.SetDatabase(databaseName); } } this._database = databaseName; } internal void SetState(ConnectionState newConnectionState, bool broadcast) { if (newConnectionState == this.connectionState && !broadcast) { return; } ConnectionState oldConnectionState = this.connectionState; this.connectionState = newConnectionState; if (broadcast) { this.OnStateChange(new StateChangeEventArgs(oldConnectionState, this.connectionState)); } } /// /// Pings the server. /// /// true if the ping was successful; otherwise, false. public bool Ping() { if (this.Reader != null) { this.Throw(new MySqlException(Resources.DataReaderOpen)); } if (this.driver != null && this.driver.Ping()) { return true; } this.driver = null; this.SetState(ConnectionState.Closed, true); return false; } /// public override void Open() { if (this.State == ConnectionState.Open) { this.Throw(new InvalidOperationException(Resources.ConnectionAlreadyOpen)); } // start up our interceptors this._exceptionInterceptor = new ExceptionInterceptor(this); this.commandInterceptor = new CommandInterceptor(this); this.SetState(ConnectionState.Connecting, true); this.AssertPermissions(); // TODO: SUPPORT FOR 452 AND 46X // if we are auto enlisting in a current transaction, then we will be // treating the connection as pooled if (this.Settings.AutoEnlist && Transaction.Current != null) { this.driver = DriverTransactionManager.GetDriverInTransaction(Transaction.Current); if (this.driver != null && (this.driver.IsInActiveUse || !this.driver.Settings.EquivalentTo(this.Settings))) { this.Throw(new NotSupportedException(Resources.MultipleConnectionsInTransactionNotSupported)); } } MySqlConnectionStringBuilder currentSettings = this.Settings; try { if (!this.Settings.Pooling || MySqlPoolManager.Hosts == null) { FailoverManager.Reset(); if (this.Settings.DnsSrv) { var dnsSrvRecords = DnsResolver.GetDnsSrvRecords(this.Settings.Server); FailoverManager.SetHostList( dnsSrvRecords.ConvertAll(r => new FailoverServer(r.Target, r.Port, null)), FailoverMethod.Sequential); } else { FailoverManager.ParseHostList(this.Settings.Server, false); } } // Load balancing && Failover if (ReplicationManager.IsReplicationGroup(this.Settings.Server)) { if (this.driver == null) { ReplicationManager.GetNewConnection(this.Settings.Server, false, this); } else { currentSettings = this.driver.Settings; } } else if (FailoverManager.FailoverGroup != null && !this.Settings.Pooling) { FailoverManager.AttemptConnection(this, this.Settings.ConnectionString, out string connectionString); currentSettings.ConnectionString = connectionString; } if (this.Settings.Pooling) { if (FailoverManager.FailoverGroup != null) { FailoverManager.AttemptConnection(this, this.Settings.ConnectionString, out string connectionString, true); currentSettings.ConnectionString = connectionString; } MySqlPool pool = MySqlPoolManager.GetPool(currentSettings); if (this.driver == null || !this.driver.IsOpen) { this.driver = pool.GetConnection(); } this.ProcedureCache = pool.ProcedureCache; } else { if (this.driver == null || !this.driver.IsOpen) { this.driver = Driver.Create(currentSettings); } this.ProcedureCache = new ProcedureCache((int)this.Settings.ProcedureCacheSize); } } catch (Exception) { this.SetState(ConnectionState.Closed, true); throw; } this.SetState(ConnectionState.Open, false); this.driver.Configure(this); if (this.driver.IsPasswordExpired && this.Settings.Pooling) { MySqlPoolManager.ClearPool(currentSettings); } if (!(this.driver.SupportsPasswordExpiration && this.driver.IsPasswordExpired)) { if (!string.IsNullOrEmpty(this.Settings.Database)) { this.ChangeDatabase(this.Settings.Database); } } // setup our schema provider this._schemaProvider = new ISSchemaProvider(this); this.PerfMonitor = new PerformanceMonitor(this); // if we are opening up inside a current transaction, then autoenlist // TODO: control this with a connection string option if (Transaction.Current != null && this.Settings.AutoEnlist) { this.EnlistTransaction(Transaction.Current); } this.hasBeenOpen = true; this.SetState(ConnectionState.Open, true); } /// public new MySqlCommand CreateCommand() { // Return a new instance of a command object. MySqlCommand c = new MySqlCommand(); c.Connection = this; return c; } internal void Abort() { try { this.driver.Close(); } catch (Exception ex) { MySqlTrace.LogWarning(this.ServerThread, String.Concat("Error occurred aborting the connection. Exception was: ", ex.Message)); } finally { this.IsInUse = false; } this.SetState(ConnectionState.Closed, true); } internal void CloseFully() { if (this.Settings.Pooling && this.driver.IsOpen) { // TODO: SUPPORT FOR 452 AND 46X //// if we are in a transaction, roll it back if (this.driver.HasStatus(ServerStatusFlags.InTransaction)) { MySqlTransaction t = new MySql.Data.MySqlClient.MySqlTransaction(this, IsolationLevel.Unspecified); t.Rollback(); } MySqlPoolManager.ReleaseConnection(this.driver); } else { this.driver.Close(); } this.driver = null; } /// public override void Close() { if (this.driver != null) { this.driver.IsPasswordExpired = false; } if (this.State == ConnectionState.Closed) { return; } if (this.Reader != null) { this.Reader.Close(); } // if the reader was opened with CloseConnection then driver // will be null on the second time through if (this.driver != null) { // TODO: Add support for 452 and 46X if (this.driver.currentTransaction == null) { this.CloseFully(); } // TODO: Add support for 452 and 46X else { this.driver.IsInActiveUse = false; } } FailoverManager.Reset(); MySqlPoolManager.Hosts = null; this.SetState(ConnectionState.Closed, true); } internal string CurrentDatabase() { if (!string.IsNullOrEmpty(this.Database)) { return this.Database; } MySqlCommand cmd = new MySqlCommand("SELECT database()", this); return cmd.ExecuteScalar().ToString(); } internal void HandleTimeoutOrThreadAbort(Exception ex) { bool isFatal = false; if (this._isKillQueryConnection) { // Special connection started to cancel a query. // Abort will prevent recursive connection spawning this.Abort(); if (ex is TimeoutException) { this.Throw(new MySqlException(Resources.Timeout, true, ex)); } else { return; } } try { // Do a fast cancel.The reason behind small values for connection // and command timeout is that we do not want user to wait longer // after command has already expired. // Microsoft's SqlClient seems to be using 5 seconds timeouts // here as well. // Read the error packet with "interrupted" message. this.CancelQuery(5); this.driver.ResetTimeout(5000); if (this.Reader != null) { this.Reader.Close(); this.Reader = null; } } catch (Exception ex2) { MySqlTrace.LogWarning(this.ServerThread, "Could not kill query, " + " aborting connection. Exception was " + ex2.Message); this.Abort(); isFatal = true; } if (ex is TimeoutException) { this.Throw(new MySqlException(Resources.Timeout, isFatal, ex)); } } /// /// Cancels the query after the specified time interval. /// /// The length of time (in seconds) to wait for the cancelation of the command execution. public void CancelQuery(int timeout) { var cb = new MySqlConnectionStringBuilder(this.Settings.ConnectionString); cb.Pooling = false; cb.AutoEnlist = false; cb.ConnectionTimeout = (uint)timeout; using (MySqlConnection c = new MySqlConnection(cb.ConnectionString)) { c._isKillQueryConnection = true; c.Open(); string commandText = "KILL QUERY " + this.ServerThread; MySqlCommand cmd = new MySqlCommand(commandText, c) { CommandTimeout = timeout }; cmd.ExecuteNonQuery(); } } #region Routines for timeout support. // Problem description: // Sometimes, ExecuteReader is called recursively. This is the case if // command behaviors are used and we issue "set sql_select_limit" // before and after command. This is also the case with prepared // statements , where we set session variables. In these situations, we // have to prevent recursive ExecuteReader calls from overwriting // timeouts set by the top level command. // To solve the problem, SetCommandTimeout() and ClearCommandTimeout() are // introduced . Query timeout here is "sticky", that is once set with // SetCommandTimeout, it only be overwritten after ClearCommandTimeout // (SetCommandTimeout would return false if it timeout has not been // cleared). // The proposed usage pattern of there routines is following: // When timed operations starts, issue SetCommandTimeout(). When it // finishes, issue ClearCommandTimeout(), but _only_ if call to // SetCommandTimeout() was successful. /// /// Sets query timeout. If timeout has been set prior and not /// yet cleared ClearCommandTimeout(), it has no effect. /// /// timeout in seconds /// true if internal bool SetCommandTimeout(int value) { if (!this.hasBeenOpen) { // Connection timeout is handled by driver return false; } if (this._commandTimeout != 0) { // someone is trying to set a timeout while command is already // running. It could be for example recursive call to ExecuteReader // Ignore the request, as only top-level (non-recursive commands) // can set timeouts. return false; } if (this.driver == null) { return false; } this._commandTimeout = value; this.driver.ResetTimeout(this._commandTimeout * 1000); return true; } /// /// Clears query timeout, allowing next SetCommandTimeout() to succeed. /// internal void ClearCommandTimeout() { if (!this.hasBeenOpen) { return; } this._commandTimeout = 0; this.driver?.ResetTimeout(0); } #endregion /// /// Gets a schema collection based on the provided restriction values. /// /// The name of the collection. /// The values to restrict. /// A schema collection object. public MySqlSchemaCollection GetSchemaCollection(string collectionName, string[] restrictionValues) { if (collectionName == null) { collectionName = SchemaProvider.MetaCollection; } string[] restrictions = this._schemaProvider.CleanRestrictions(restrictionValues); MySqlSchemaCollection c = this._schemaProvider.GetSchema(collectionName, restrictions); return c; } #region Pool Routines /// public static void ClearPool(MySqlConnection connection) { MySqlPoolManager.ClearPool(connection.Settings); } /// public static void ClearAllPools() { MySqlPoolManager.ClearAllPools(); } #endregion internal void Throw(Exception ex) { if (this._exceptionInterceptor == null) { throw ex; } this._exceptionInterceptor.Throw(ex); } public new void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } #region Async /// /// Initiates the asynchronous execution of a transaction. /// /// An object representing the new transaction. public Task BeginTransactionAsync() { return this.BeginTransactionAsync(IsolationLevel.RepeatableRead, CancellationToken.None); } /// /// Asynchronous version of BeginTransaction. /// /// The cancellation token. /// An object representing the new transaction. #pragma warning disable CS0108 // “MySqlConnection.BeginTransactionAsync(CancellationToken)”隐藏继承的成员“DbConnection.BeginTransactionAsync(CancellationToken)”。如果是有意隐藏,请使用关键字 new。 public Task BeginTransactionAsync(CancellationToken cancellationToken) #pragma warning restore CS0108 // “MySqlConnection.BeginTransactionAsync(CancellationToken)”隐藏继承的成员“DbConnection.BeginTransactionAsync(CancellationToken)”。如果是有意隐藏,请使用关键字 new。 { return this.BeginTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken); } /// /// Asynchronous version of BeginTransaction. /// /// The isolation level under which the transaction should run. /// An object representing the new transaction. public Task BeginTransactionAsync(IsolationLevel iso) { return this.BeginTransactionAsync(iso, CancellationToken.None); } /// /// Asynchronous version of BeginTransaction. /// /// The isolation level under which the transaction should run. /// The cancellation token. /// An object representing the new transaction. #pragma warning disable CS0108 // “MySqlConnection.BeginTransactionAsync(IsolationLevel, CancellationToken)”隐藏继承的成员“DbConnection.BeginTransactionAsync(IsolationLevel, CancellationToken)”。如果是有意隐藏,请使用关键字 new。 public Task BeginTransactionAsync(IsolationLevel iso, CancellationToken cancellationToken) #pragma warning restore CS0108 // “MySqlConnection.BeginTransactionAsync(IsolationLevel, CancellationToken)”隐藏继承的成员“DbConnection.BeginTransactionAsync(IsolationLevel, CancellationToken)”。如果是有意隐藏,请使用关键字 new。 { var result = new TaskCompletionSource(); if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested) { try { MySqlTransaction tranResult = this.BeginTransaction(iso); result.SetResult(tranResult); } catch (Exception ex) { result.SetException(ex); } } else { result.SetCanceled(); } return result.Task; } /// /// Asynchronous version of the ChangeDataBase method. /// /// The name of the database to use. /// public Task ChangeDataBaseAsync(string databaseName) { return this.ChangeDataBaseAsync(databaseName, CancellationToken.None); } /// /// Asynchronous version of the ChangeDataBase method. /// /// The name of the database to use. /// The cancellation token. /// public Task ChangeDataBaseAsync(string databaseName, CancellationToken cancellationToken) { var result = new TaskCompletionSource(); if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested) { try { this.ChangeDatabase(databaseName); result.SetResult(true); } catch (Exception ex) { result.SetException(ex); } } return result.Task; } ///// ///// Async version of Open ///// ///// // public Task OpenAsync() // { // return Task.Run(() => // { // Open(); // }); // } /// /// Asynchronous version of the Close method. /// public new Task CloseAsync() { return this.CloseAsync(CancellationToken.None); } /// /// Asynchronous version of the Close method. /// /// The cancellation token. public Task CloseAsync(CancellationToken cancellationToken) { var result = new TaskCompletionSource(); if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested) { try { this.Close(); result.SetResult(true); } catch (Exception ex) { result.SetException(ex); } } else { result.SetCanceled(); } return result.Task; } /// /// Asynchronous version of the ClearPool method. /// /// The connection associated with the pool to be cleared. public Task ClearPoolAsync(MySqlConnection connection) { return this.ClearPoolAsync(connection, CancellationToken.None); } /// /// Asynchronous version of the ClearPool method. /// /// The connection associated with the pool to be cleared. /// The cancellation token. public Task ClearPoolAsync(MySqlConnection connection, CancellationToken cancellationToken) { var result = new TaskCompletionSource(); if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested) { try { ClearPool(connection); result.SetResult(true); } catch (Exception ex) { result.SetException(ex); } } else { result.SetCanceled(); } return result.Task; } /// /// Asynchronous version of the ClearAllPools method. /// public Task ClearAllPoolsAsync() { return this.ClearAllPoolsAsync(CancellationToken.None); } /// /// Asynchronous version of the ClearAllPools method. /// /// The cancellation token. public Task ClearAllPoolsAsync(CancellationToken cancellationToken) { var result = new TaskCompletionSource(); if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested) { try { ClearAllPools(); result.SetResult(true); } catch (Exception ex) { result.SetException(ex); } } else { result.SetCanceled(); } return result.Task; } /// /// Asynchronous version of the GetSchemaCollection method. /// /// The name of the collection. /// The values to restrict. /// A collection of schema objects. public Task GetSchemaCollectionAsync(string collectionName, string[] restrictionValues) { return this.GetSchemaCollectionAsync(collectionName, restrictionValues, CancellationToken.None); } /// /// Asynchronous version of the GetSchemaCollection method. /// /// The name of the collection. /// The values to restrict. /// The cancellation token. /// A collection of schema objects. public Task GetSchemaCollectionAsync(string collectionName, string[] restrictionValues, CancellationToken cancellationToken) { var result = new TaskCompletionSource(); if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested) { try { var schema = this.GetSchemaCollection(collectionName, restrictionValues); result.SetResult(schema); } catch (Exception ex) { result.SetException(ex); } } else { result.SetCanceled(); } return result.Task; } #endregion } /// /// Represents the method that will handle the event of a /// . /// public delegate void MySqlInfoMessageEventHandler(object sender, MySqlInfoMessageEventArgs args); /// /// Provides data for the InfoMessage event. This class cannot be inherited. /// public class MySqlInfoMessageEventArgs : EventArgs { /// /// Gets or sets an array of objects set with the errors found. /// public MySqlError[] errors { get; set; } } /// /// IDisposable wrapper around SetCommandTimeout and ClearCommandTimeout functionality. /// internal class CommandTimer : IDisposable { private bool _timeoutSet; private MySqlConnection _connection; public CommandTimer(MySqlConnection connection, int timeout) { this._connection = connection; if (connection != null) { this._timeoutSet = connection.SetCommandTimeout(timeout); } } #region IDisposable Members public void Dispose() { if (!this._timeoutSet) { return; } this._timeoutSet = false; this._connection.ClearCommandTimeout(); this._connection = null; } #endregion } }