diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml index a79ccebf4e..4dbe1f6511 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml @@ -1240,6 +1240,7 @@ For example, with a 30 second time out, if + The value set is less than 0. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml index 8e7e927187..24aac534e4 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml @@ -472,6 +472,23 @@ The connection string contains . The list of trusted master key paths for the column encryption. To be added. + + + Gets the default wait time (in seconds) before terminating the attempt to execute a command and generating an error. The default is 30 seconds. + + + The time in seconds to wait for the command to execute. The default is 30 seconds. + + + + + Gets or sets the string used to open a SQL Server database. The connection string that includes the source database name, and other parameters needed to establish the initial connection. The default value is an empty string. @@ -517,6 +534,7 @@ The connection string contains . |AttachDBFilename

-or-

Extended Properties

-or-

Initial File Name|N/A|The name of the primary database file, including the full path name of an attachable database. AttachDBFilename is only supported for primary data files with an .mdf extension.

If the value of the AttachDBFileName key is specified in the connection string, the database is attached and becomes the default database for the connection.

If this key is not specified and if the database was previously attached, the database will not be reattached. The previously attached database will be used as the default database for the connection.

If this key is specified together with the AttachDBFileName key, the value of this key will be used as the alias. However, if the name is already used in another attached database, the connection will fail.

The path may be absolute or relative by using the DataDirectory substitution string. If DataDirectory is used, the database file must exist within a subdirectory of the directory pointed to by the substitution string. **Note:** Remote server, HTTP, and UNC path names are not supported.

The database name must be specified with the keyword 'database' (or one of its aliases) as in the following:

"AttachDbFileName=|DataDirectory|\data\YourDB.mdf;integrated security=true;database=YourDatabase"

An error will be generated if a log file exists in the same directory as the data file and the 'database' keyword is used when attaching the primary data file. In this case, remove the log file. Once the database is attached, a new log file will be automatically generated based on the physical path.| |Authentication|N/A|The authentication method used for [Connecting to SQL Database By Using Azure Active Directory Authentication](https://azure.microsoft.com/documentation/articles/sql-database-aad-authentication/#7-connect-to-your-database-by-using-azure-active-directory-identities).

Valid values are:

`Active Directory Integrated`, `Active Directory Interactive`, `Active Directory Password`, `Sql Password`. Currently `Active Directory Integrated` and `Active Directory Interactive` modes of authentication are supported only for .NET Framework. | |Column Encryption Setting|N/A|Enables or disables [Always Encrypted](/sql/relational-databases/security/encryption/always-encrypted-database-engine?view=sql-server-2017) functionality for the connection.| +|Command Timeout|30|The default wait time (in seconds) before terminating the attempt to execute a command and generating an error.

Valid values are greater than or equal to 0 and less than or equal to 2147483647.| |Connect Timeout

-or-

Connection Timeout

-or-

Timeout|15|The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error.

Valid values are greater than or equal to 0 and less than or equal to 2147483647.

When opening a connection to a Azure SQL Database, set the connection timeout to 30 seconds.| |Connection Lifetime

-or-

Load Balance Timeout|0|When a connection is returned to the pool, its creation time is compared with the current time, and the connection is destroyed if that time span (in seconds) exceeds the value specified by `Connection Lifetime`. This is useful in clustered configurations to force load balancing between a running server and a server just brought online.

A value of zero (0) causes pooled connections to have the maximum connection timeout.| |Connect Retry Count

-or-

ConnectRetryCount|1|Controls the number of reconnection attempts after the client identifies an idle connection failure. Valid values are 0 to 255. The default is 1. 0 means do not attempt to reconnect (disable connection resiliency).

For additional information about idle connection resiliency, see [Technical Article - Idle Connection Resiliency](https://go.microsoft.com/fwlink/?LinkId=393996).| diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml index 1992da198a..98d4d5cc70 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -195,6 +195,23 @@ Modified: Data Source=(local);Initial Catalog=AdventureWorks;Integrated Security The column encryption settings for the connection string builder. To be added. + + + The default wait time (in seconds) before terminating the attempt to execute a command and generating an error. The default is 30 seconds. + + + The time in seconds to wait for the command to execute. The default is 30 seconds. + + + connection string. + + ]]> + + The value set is less than 0. + Obsolete. Gets or sets a Boolean value that indicates whether the connection is reset when drawn from the connection pool. The value of the property, or true if no value has been supplied. diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index 47c806d387..3a75ca8d09 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -590,6 +590,9 @@ public sealed partial class SqlConnection : System.Data.Common.DbConnection, Sys internal string SQLDNSCachingSupportedStateBeforeRedirect { get { throw null; } } object System.ICloneable.Clone() { throw null; } + /// + [System.ComponentModel.DesignerSerializationVisibilityAttribute(0)] + public int CommandTimeout { get { throw null; } } /// [System.ComponentModel.DefaultValueAttribute("")] [System.ComponentModel.EditorAttribute("Microsoft.VSDesigner.Data.SQL.Design.SqlConnectionStringEditor, Microsoft.VSDesigner, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] @@ -712,6 +715,10 @@ public sealed partial class SqlConnectionStringBuilder : System.Data.Common.DbCo [System.ComponentModel.DisplayNameAttribute("Authentication")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public Microsoft.Data.SqlClient.SqlAuthenticationMethod Authentication { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Command Timeout")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public int CommandTimeout { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Connect Retry Count")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index 7bd9f2c909..66a04e5ff5 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -487,7 +487,7 @@ internal static ApplicationIntent ConvertToApplicationIntent(string keyword, obj internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod value) { - Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 6, "SqlAuthenticationMethod enum has changed, update needed"); + Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 7, "SqlAuthenticationMethod enum has changed, update needed"); return value == SqlAuthenticationMethod.SqlPassword || value == SqlAuthenticationMethod.ActiveDirectoryPassword || value == SqlAuthenticationMethod.ActiveDirectoryIntegrated @@ -676,6 +676,7 @@ internal static partial class DbConnectionStringDefaults internal const ApplicationIntent ApplicationIntent = Microsoft.Data.SqlClient.ApplicationIntent.ReadWrite; internal const string ApplicationName = "Core Microsoft SqlClient Data Provider"; internal const string AttachDBFilename = ""; + internal const int CommandTimeout = 30; internal const int ConnectTimeout = 15; internal const string CurrentLanguage = ""; internal const string DataSource = ""; @@ -719,6 +720,7 @@ internal static partial class DbConnectionStringKeywords internal const string ApplicationName = "Application Name"; internal const string AsynchronousProcessing = "Asynchronous Processing"; internal const string AttachDBFilename = "AttachDbFilename"; + internal const string CommandTimeout = "Command Timeout"; internal const string ConnectTimeout = "Connect Timeout"; internal const string ConnectionReset = "Connection Reset"; internal const string ContextConnection = "Context Connection"; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs index 526459baca..ae7db9afeb 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -65,7 +65,7 @@ protected override void AfterCleared(SqlCommand owner) } private CommandType _commandType; - private int _commandTimeout = ADP.DefaultCommandTimeout; + private int? _commandTimeout; private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; private bool _designTimeInvisible; @@ -572,7 +572,7 @@ override public int CommandTimeout { get { - return _commandTimeout; + return _commandTimeout ?? DefaultCommandTimeout; } set { @@ -592,10 +592,18 @@ override public int CommandTimeout /// public void ResetCommandTimeout() { - if (ADP.DefaultCommandTimeout != _commandTimeout) + if (ADP.DefaultCommandTimeout != CommandTimeout) { PropertyChanging(); - _commandTimeout = ADP.DefaultCommandTimeout; + _commandTimeout = DefaultCommandTimeout; + } + } + + private int DefaultCommandTimeout + { + get + { + return _activeConnection?.CommandTimeout ?? ADP.DefaultCommandTimeout; } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs index c1fa77b989..d120c08634 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -505,6 +505,16 @@ public override int ConnectionTimeout } } + /// + public int CommandTimeout + { + get + { + SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; + return ((null != constr) ? constr.CommandTimeout : SqlConnectionString.DEFAULT.Command_Timeout); + } + } + /// // AccessToken: To be used for token based authentication public string AccessToken diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs index 90010dc058..9a87f2feed 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -23,6 +23,7 @@ internal static partial class DEFAULT internal const ApplicationIntent ApplicationIntent = DbConnectionStringDefaults.ApplicationIntent; internal const string Application_Name = TdsEnums.SQL_PROVIDER_NAME; internal const string AttachDBFilename = ""; + internal const int Command_Timeout = ADP.DefaultCommandTimeout; internal const int Connect_Timeout = ADP.DefaultConnectionTimeout; internal const string Current_Language = ""; internal const string Data_Source = ""; @@ -67,6 +68,8 @@ internal static class KEY internal const string ColumnEncryptionSetting = "column encryption setting"; internal const string EnclaveAttestationUrl = "enclave attestation url"; internal const string AttestationProtocol = "attestation protocol"; + + internal const string Command_Timeout = "command timeout"; internal const string Connect_Timeout = "connect timeout"; internal const string Connection_Reset = "connection reset"; internal const string Context_Connection = "context connection"; @@ -210,6 +213,7 @@ internal static class TRANSACTIONBINDING private readonly string _enclaveAttestationUrl; private readonly SqlConnectionAttestationProtocol _attestationProtocol; + private readonly int _commandTimeout; private readonly int _connectTimeout; private readonly int _loadBalanceTimeout; private readonly int _maxPoolSize; @@ -265,6 +269,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G _userInstance = ConvertValueToBoolean(KEY.User_Instance, DEFAULT.User_Instance); _multiSubnetFailover = ConvertValueToBoolean(KEY.MultiSubnetFailover, DEFAULT.MultiSubnetFailover); + _commandTimeout = ConvertValueToInt32(KEY.Command_Timeout, DEFAULT.Command_Timeout); _connectTimeout = ConvertValueToInt32(KEY.Connect_Timeout, DEFAULT.Connect_Timeout); _loadBalanceTimeout = ConvertValueToInt32(KEY.Load_Balance_Timeout, DEFAULT.Load_Balance_Timeout); _maxPoolSize = ConvertValueToInt32(KEY.Max_Pool_Size, DEFAULT.Max_Pool_Size); @@ -307,6 +312,11 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G throw ADP.InvalidConnectionOptionValue(KEY.Connect_Timeout); } + if (_commandTimeout < 0) + { + throw ADP.InvalidConnectionOptionValue(KEY.Command_Timeout); + } + if (_maxPoolSize < 1) { throw ADP.InvalidConnectionOptionValue(KEY.Max_Pool_Size); @@ -490,6 +500,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS _pooling = connectionOptions._pooling; _replication = connectionOptions._replication; _userInstance = userInstance; + _commandTimeout = connectionOptions._commandTimeout; _connectTimeout = connectionOptions._connectTimeout; _loadBalanceTimeout = connectionOptions._loadBalanceTimeout; #if netcoreapp @@ -545,6 +556,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS internal bool Replication { get { return _replication; } } internal bool UserInstance { get { return _userInstance; } } + internal int CommandTimeout { get { return _commandTimeout; } } internal int ConnectTimeout { get { return _connectTimeout; } } internal int LoadBalanceTimeout { get { return _loadBalanceTimeout; } } internal int MaxPoolSize { get { return _maxPoolSize; } } @@ -633,6 +645,7 @@ private static bool CompareHostName(ref string host, string name, bool fixup) #if netcoreapp { KEY.PoolBlockingPeriod, KEY.PoolBlockingPeriod}, #endif + { KEY.Command_Timeout, KEY.Command_Timeout }, { KEY.Connect_Timeout, KEY.Connect_Timeout }, { KEY.Connection_Reset, KEY.Connection_Reset }, { KEY.Context_Connection, KEY.Context_Connection }, diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs index 2353243a1a..a2a327ea51 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -69,6 +69,8 @@ private enum Keywords EnclaveAttestationUrl, AttestationProtocol, + CommandTimeout, + // keep the count value last KeywordsCount } @@ -93,6 +95,7 @@ private enum Keywords private string _userID = DbConnectionStringDefaults.UserID; private string _workstationID = DbConnectionStringDefaults.WorkstationID; + private int _commandTimeout = DbConnectionStringDefaults.CommandTimeout; private int _connectTimeout = DbConnectionStringDefaults.ConnectTimeout; private int _loadBalanceTimeout = DbConnectionStringDefaults.LoadBalanceTimeout; private int _maxPoolSize = DbConnectionStringDefaults.MaxPoolSize; @@ -125,6 +128,7 @@ private static string[] CreateValidKeywords() #if netcoreapp validKeywords[(int)Keywords.PoolBlockingPeriod] = DbConnectionStringKeywords.PoolBlockingPeriod; #endif + validKeywords[(int)Keywords.CommandTimeout] = DbConnectionStringKeywords.CommandTimeout; validKeywords[(int)Keywords.ConnectTimeout] = DbConnectionStringKeywords.ConnectTimeout; validKeywords[(int)Keywords.CurrentLanguage] = DbConnectionStringKeywords.CurrentLanguage; validKeywords[(int)Keywords.DataSource] = DbConnectionStringKeywords.DataSource; @@ -168,6 +172,7 @@ private static string[] CreateValidKeywords() #if netcoreapp hash.Add(DbConnectionStringKeywords.PoolBlockingPeriod, Keywords.PoolBlockingPeriod); #endif + hash.Add(DbConnectionStringKeywords.CommandTimeout, Keywords.CommandTimeout); hash.Add(DbConnectionStringKeywords.ConnectTimeout, Keywords.ConnectTimeout); hash.Add(DbConnectionStringKeywords.CurrentLanguage, Keywords.CurrentLanguage); hash.Add(DbConnectionStringKeywords.DataSource, Keywords.DataSource); @@ -298,6 +303,9 @@ public SqlConnectionStringBuilder(string connectionString) : base() WorkstationID = ConvertToString(value); break; + case Keywords.CommandTimeout: + CommandTimeout = ConvertToInt32(value); + break; case Keywords.ConnectTimeout: ConnectTimeout = ConvertToInt32(value); break; @@ -416,6 +424,21 @@ public string AttachDBFilename } } + /// + public int CommandTimeout + { + get { return _commandTimeout; } + set + { + if (value < 0) + { + throw ADP.InvalidConnectionOptionValue(DbConnectionStringKeywords.CommandTimeout); + } + SetValue(DbConnectionStringKeywords.CommandTimeout, value); + _commandTimeout = value; + } + } + /// public int ConnectTimeout { @@ -905,6 +928,8 @@ private object GetAt(Keywords index) #if netcoreapp case Keywords.PoolBlockingPeriod: return PoolBlockingPeriod; #endif + case Keywords.CommandTimeout: + return CommandTimeout; case Keywords.ConnectTimeout: return ConnectTimeout; case Keywords.CurrentLanguage: @@ -1021,6 +1046,9 @@ private void Reset(Keywords index) _poolBlockingPeriod = DbConnectionStringDefaults.PoolBlockingPeriod; break; #endif + case Keywords.CommandTimeout: + _commandTimeout = DbConnectionStringDefaults.CommandTimeout; + break; case Keywords.ConnectTimeout: _connectTimeout = DbConnectionStringDefaults.ConnectTimeout; break; diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index 1fe8a00ea2..6f29fe813c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -744,6 +744,9 @@ public sealed partial class SqlConnection : System.Data.Common.DbConnection, Sys /// [System.ComponentModel.DefaultValueAttribute(null)] public static System.Collections.Generic.IDictionary> ColumnEncryptionTrustedMasterKeyPaths { get { throw null; } } + /// + [System.ComponentModel.DesignerSerializationVisibilityAttribute(0)] + public int CommandTimeout { get { throw null; } } /// [System.ComponentModel.DefaultValueAttribute("")] [System.ComponentModel.EditorAttribute("Microsoft.VSDesigner.Data.SQL.Design.SqlConnectionStringEditor, Microsoft.VSDesigner, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] @@ -912,6 +915,10 @@ public sealed partial class SqlConnectionStringBuilder : System.Data.Common.DbCo [System.ComponentModel.DisplayNameAttribute("Column Encryption Setting")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public Microsoft.Data.SqlClient.SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Command Timeout")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public int CommandTimeout { get { throw null; } set { } } /// [System.ComponentModel.BrowsableAttribute(false)] [System.ComponentModel.DisplayNameAttribute("Connection Reset")] diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index 9979e9f6de..b3ba200bdd 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -648,7 +648,7 @@ internal static string ColumnEncryptionSettingToString(SqlConnectionColumnEncryp internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod value) { - Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 6, "SqlAuthenticationMethod enum has changed, update needed"); + Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 7, "SqlAuthenticationMethod enum has changed, update needed"); return value == SqlAuthenticationMethod.SqlPassword || value == SqlAuthenticationMethod.ActiveDirectoryPassword || value == SqlAuthenticationMethod.ActiveDirectoryIntegrated @@ -1011,6 +1011,7 @@ internal static class DbConnectionStringDefaults internal const string ApplicationName = "Framework Microsoft SqlClient Data Provider"; internal const bool AsynchronousProcessing = false; internal const string AttachDBFilename = ""; + internal const int CommandTimeout = 30; internal const int ConnectTimeout = 15; internal const bool ConnectionReset = true; internal const bool ContextConnection = false; @@ -1094,6 +1095,7 @@ internal static class DbConnectionStringKeywords internal const string AsynchronousProcessing = "Asynchronous Processing"; internal const string AttachDBFilename = "AttachDbFilename"; internal const string ConnectTimeout = "Connect Timeout"; + internal const string CommandTimeout = "Command Timeout"; internal const string ConnectionReset = "Connection Reset"; internal const string ContextConnection = "Context Connection"; internal const string CurrentLanguage = "Current Language"; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index d0a9d96be7..dfe71c8351 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -39,7 +39,7 @@ public sealed class SqlCommand : DbCommand, ICloneable private string _commandText; private CommandType _commandType; - private int _commandTimeout = ADP.DefaultCommandTimeout; + private int? _commandTimeout; private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; private bool _designTimeInvisible; @@ -762,7 +762,7 @@ override public int CommandTimeout { // V1.2.3300, XXXCommand V1.0.5000 get { - return _commandTimeout; + return _commandTimeout ?? DefaultCommandTimeout; } set { @@ -783,16 +783,19 @@ override public int CommandTimeout /// public void ResetCommandTimeout() { // V1.2.3300 - if (ADP.DefaultCommandTimeout != _commandTimeout) + if (ADP.DefaultCommandTimeout != CommandTimeout) { PropertyChanging(); - _commandTimeout = ADP.DefaultCommandTimeout; + _commandTimeout = DefaultCommandTimeout; } } - private bool ShouldSerializeCommandTimeout() - { // V1.2.3300 - return (ADP.DefaultCommandTimeout != _commandTimeout); + private int DefaultCommandTimeout + { + get + { + return _activeConnection?.CommandTimeout ?? ADP.DefaultCommandTimeout; + } } /// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs index 491ff0cc0a..6682ccbb90 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -642,6 +642,20 @@ public string AccessToken } } + /// + [ + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), + ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_ConnectionTimeout), + ] + public int CommandTimeout + { + get + { + SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; + return ((null != constr) ? constr.CommandTimeout : SqlConnectionString.DEFAULT.Command_Timeout); + } + } + /// [ DefaultValue(""), diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs index d696512b51..2f24e0dabd 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -24,6 +24,7 @@ internal static class DEFAULT internal const bool Asynchronous = false; internal const string AttachDBFilename = ""; internal const PoolBlockingPeriod PoolBlockingPeriod = DbConnectionStringDefaults.PoolBlockingPeriod; + internal const int Command_Timeout = ADP.DefaultCommandTimeout; internal const int Connect_Timeout = ADP.DefaultConnectionTimeout; internal const bool Connection_Reset = true; internal const bool Context_Connection = false; @@ -74,6 +75,7 @@ internal static class KEY internal const string EnclaveAttestationUrl = "enclave attestation url"; internal const string AttestationProtocol = "attestation protocol"; internal const string Connect_Timeout = "connect timeout"; + internal const string Command_Timeout = "command timeout"; internal const string Connection_Reset = "connection reset"; internal const string Context_Connection = "context connection"; internal const string Current_Language = "current language"; @@ -235,6 +237,7 @@ internal static class TRANSACIONBINDING private readonly string _enclaveAttestationUrl; private readonly SqlConnectionAttestationProtocol _attestationProtocol; + private readonly int _commandTimeout; private readonly int _connectTimeout; private readonly int _loadBalanceTimeout; private readonly int _maxPoolSize; @@ -296,6 +299,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G _transparentNetworkIPResolution = ConvertValueToBoolean(KEY.TransparentNetworkIPResolution, DEFAULT.TransparentNetworkIPResolution); _connectTimeout = ConvertValueToInt32(KEY.Connect_Timeout, DEFAULT.Connect_Timeout); + _commandTimeout = ConvertValueToInt32(KEY.Command_Timeout, DEFAULT.Command_Timeout); _loadBalanceTimeout = ConvertValueToInt32(KEY.Load_Balance_Timeout, DEFAULT.Load_Balance_Timeout); _maxPoolSize = ConvertValueToInt32(KEY.Max_Pool_Size, DEFAULT.Max_Pool_Size); _minPoolSize = ConvertValueToInt32(KEY.Min_Pool_Size, DEFAULT.Min_Pool_Size); @@ -376,6 +380,11 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G throw ADP.InvalidConnectionOptionValue(KEY.Connect_Timeout); } + if (_commandTimeout < 0) + { + throw ADP.InvalidConnectionOptionValue(KEY.Command_Timeout); + } + if (_maxPoolSize < 1) { throw ADP.InvalidConnectionOptionValue(KEY.Max_Pool_Size); @@ -601,6 +610,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS _pooling = connectionOptions._pooling; _replication = connectionOptions._replication; _userInstance = userInstance; + _commandTimeout = connectionOptions._commandTimeout; _connectTimeout = connectionOptions._connectTimeout; _loadBalanceTimeout = connectionOptions._loadBalanceTimeout; _poolBlockingPeriod = connectionOptions._poolBlockingPeriod; @@ -671,6 +681,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS internal bool Replication { get { return _replication; } } internal bool UserInstance { get { return _userInstance; } } + internal int CommandTimeout { get { return _commandTimeout; } } internal int ConnectTimeout { get { return _connectTimeout; } } internal int LoadBalanceTimeout { get { return _loadBalanceTimeout; } } internal int MaxPoolSize { get { return _maxPoolSize; } } @@ -764,6 +775,7 @@ internal static Hashtable GetParseSynonyms() hash.Add(KEY.AttachDBFilename, KEY.AttachDBFilename); hash.Add(KEY.PoolBlockingPeriod, KEY.PoolBlockingPeriod); hash.Add(KEY.Connect_Timeout, KEY.Connect_Timeout); + hash.Add(KEY.Command_Timeout, KEY.Command_Timeout); hash.Add(KEY.Connection_Reset, KEY.Connection_Reset); hash.Add(KEY.Context_Connection, KEY.Context_Connection); hash.Add(KEY.Current_Language, KEY.Current_Language); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs index 5cf2cfdb9a..fb17b8df03 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -81,6 +81,8 @@ private enum Keywords EnclaveAttestationUrl, AttestationProtocol, + CommandTimeout, + #if ADONET_CERT_AUTH Certificate, #endif @@ -108,6 +110,7 @@ private enum Keywords private string _userID = DbConnectionStringDefaults.UserID; private string _workstationID = DbConnectionStringDefaults.WorkstationID; + private int _commandTimeout = DbConnectionStringDefaults.CommandTimeout; private int _connectTimeout = DbConnectionStringDefaults.ConnectTimeout; private int _loadBalanceTimeout = DbConnectionStringDefaults.LoadBalanceTimeout; private int _maxPoolSize = DbConnectionStringDefaults.MaxPoolSize; @@ -151,6 +154,7 @@ static SqlConnectionStringBuilder() validKeywords[(int)Keywords.ConnectionReset] = DbConnectionStringKeywords.ConnectionReset; validKeywords[(int)Keywords.ContextConnection] = DbConnectionStringKeywords.ContextConnection; validKeywords[(int)Keywords.ConnectTimeout] = DbConnectionStringKeywords.ConnectTimeout; + validKeywords[(int)Keywords.CommandTimeout] = DbConnectionStringKeywords.CommandTimeout; validKeywords[(int)Keywords.CurrentLanguage] = DbConnectionStringKeywords.CurrentLanguage; validKeywords[(int)Keywords.DataSource] = DbConnectionStringKeywords.DataSource; validKeywords[(int)Keywords.Encrypt] = DbConnectionStringKeywords.Encrypt; @@ -195,6 +199,7 @@ static SqlConnectionStringBuilder() hash.Add(DbConnectionStringKeywords.AttachDBFilename, Keywords.AttachDBFilename); hash.Add(DbConnectionStringKeywords.PoolBlockingPeriod, Keywords.PoolBlockingPeriod); hash.Add(DbConnectionStringKeywords.ConnectTimeout, Keywords.ConnectTimeout); + hash.Add(DbConnectionStringKeywords.CommandTimeout, Keywords.CommandTimeout); hash.Add(DbConnectionStringKeywords.ConnectionReset, Keywords.ConnectionReset); hash.Add(DbConnectionStringKeywords.ContextConnection, Keywords.ContextConnection); hash.Add(DbConnectionStringKeywords.CurrentLanguage, Keywords.CurrentLanguage); @@ -336,6 +341,9 @@ public SqlConnectionStringBuilder(string connectionString) : base() WorkstationID = ConvertToString(value); break; + case Keywords.CommandTimeout: + CommandTimeout = ConvertToInt32(value); + break; case Keywords.ConnectTimeout: ConnectTimeout = ConvertToInt32(value); break; @@ -521,6 +529,25 @@ public PoolBlockingPeriod PoolBlockingPeriod } } + /// + [DisplayName(DbConnectionStringKeywords.CommandTimeout)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Initialization)] + [ResDescription(StringsHelper.ResourceNames.DbCommand_CommandTimeout)] + [RefreshProperties(RefreshProperties.All)] + public int CommandTimeout + { + get { return _commandTimeout; } + set + { + if (value < 0) + { + throw ADP.InvalidConnectionOptionValue(DbConnectionStringKeywords.CommandTimeout); + } + SetValue(DbConnectionStringKeywords.CommandTimeout, value); + _commandTimeout = value; + } + } + /// [Browsable(false)] [DisplayName(DbConnectionStringKeywords.ConnectionReset)] @@ -1268,6 +1295,8 @@ private object GetAt(Keywords index) return PoolBlockingPeriod; case Keywords.ConnectTimeout: return ConnectTimeout; + case Keywords.CommandTimeout: + return CommandTimeout; #pragma warning disable 618 // Obsolete ConnectionReset case Keywords.ConnectionReset: return ConnectionReset; @@ -1430,6 +1459,9 @@ private void Reset(Keywords index) case Keywords.ConnectTimeout: _connectTimeout = DbConnectionStringDefaults.ConnectTimeout; break; + case Keywords.CommandTimeout: + _commandTimeout = DbConnectionStringDefaults.CommandTimeout; + break; case Keywords.ConnectionReset: _connectionReset = DbConnectionStringDefaults.ConnectionReset; break; diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs index ae9e097dfd..a01badc8ed 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs @@ -136,6 +136,22 @@ public void Constructor3() Assert.Equal(UpdateRowSource.Both, cmd.UpdatedRowSource); } + [Theory] + [InlineData(0)] + [InlineData(30)] + [InlineData(15)] + public void Constructor3_CommandTimeout(int timeout) + { + SqlConnection conn = new SqlConnection($"Command Timeout = {timeout}"); + SqlCommand cmd; + + cmd = new SqlCommand(COMMAND_TEXT, conn); + Assert.Equal(timeout, cmd.CommandTimeout); + + cmd.CommandTimeout = timeout + 10; + Assert.Equal(timeout + 10, cmd.CommandTimeout); + } + [Fact] public void Constructor4() { diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index 449e8bca09..9f6061e40c 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -17,6 +17,9 @@ public class SqlConnectionStringBuilderTest [InlineData("Attestation Protocol = HGS")] [InlineData("Authentication = Active Directory Password ")] [InlineData("Authentication = ActiveDirectoryPassword ")] + [InlineData("Command Timeout = 5")] + [InlineData("Command Timeout = 15")] + [InlineData("Command Timeout = 0")] [InlineData("ConnectRetryCount = 5")] [InlineData("Connect Retry Count = 0")] [InlineData("ConnectRetryInterval = 10")] diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs index f2870aae1e..9289f23768 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs @@ -24,6 +24,7 @@ public void Constructor1() Assert.Equal(string.Empty, cn.ConnectionString); Assert.Equal(15, cn.ConnectionTimeout); + Assert.Null(cn.Container); Assert.Equal(string.Empty, cn.Database); Assert.Equal(string.Empty, cn.DataSource); @@ -43,6 +44,7 @@ public void Constructor2() SqlConnection cn = new SqlConnection(connectionString); Assert.Equal(connectionString, cn.ConnectionString); Assert.Equal(15, cn.ConnectionTimeout); + Assert.Equal(30, cn.CommandTimeout); Assert.Null(cn.Container); Assert.Equal("dotNet", cn.Database); Assert.Equal("SQLSRV", cn.DataSource); @@ -56,6 +58,7 @@ public void Constructor2() cn = new SqlConnection((string)null); Assert.Equal(string.Empty, cn.ConnectionString); Assert.Equal(15, cn.ConnectionTimeout); + Assert.Equal(30, cn.CommandTimeout); Assert.Null(cn.Container); Assert.Equal(string.Empty, cn.Database); Assert.Equal(string.Empty, cn.DataSource); @@ -722,6 +725,85 @@ public void ConnectionString_ConnectTimeout_Invalid() } } + [Fact] + public void ConnectionString_CommandTimeout() + { + SqlConnection cn = new SqlConnection(); + cn.ConnectionString = "Command Timeout=45"; + Assert.Equal(45, cn.CommandTimeout); + cn.ConnectionString = "Command Timeout=40"; + Assert.Equal(40, cn.CommandTimeout); + cn.ConnectionString = "command timeout="; + Assert.Equal(30, cn.CommandTimeout); + cn.ConnectionString = "Command Timeout=2147483647"; + Assert.Equal(int.MaxValue, cn.CommandTimeout); + cn.ConnectionString = "Command Timeout=0"; + Assert.Equal(0, cn.CommandTimeout); + } + + [Fact] + public void ConnectionString_CommandTimeout_Invalid() + { + SqlConnection cn = new SqlConnection(); + + // negative number + try + { + cn.ConnectionString = "Command timeout=-1"; + } + catch (ArgumentException ex) + { + // Invalid value for key 'connect timeout' + Assert.Equal(typeof(ArgumentException), ex.GetType()); + Assert.Null(ex.InnerException); + Assert.NotNull(ex.Message); + Assert.True(ex.Message.IndexOf("'command timeout'") != -1); + Assert.Null(ex.ParamName); + } + + // invalid number + try + { + cn.ConnectionString = "command Timeout=BB"; + } + catch (ArgumentException ex) + { + // Invalid value for key 'connect timeout' + Assert.Equal(typeof(ArgumentException), ex.GetType()); + Assert.NotNull(ex.InnerException); + Assert.Equal(typeof(FormatException), ex.InnerException.GetType()); + Assert.NotNull(ex.Message); + Assert.True(ex.Message.IndexOf("'command timeout'") != -13); + Assert.Null(ex.ParamName); + + // Input string was not in a correct format + FormatException fe = (FormatException)ex.InnerException; + Assert.Null(fe.InnerException); + Assert.NotNull(fe.Message); + } + + // overflow + try + { + cn.ConnectionString = "command timeout=2147483648"; + } + catch (ArgumentException ex) + { + // Invalid value for key 'command timeout' + Assert.Equal(typeof(ArgumentException), ex.GetType()); + Assert.NotNull(ex.InnerException); + Assert.Equal(typeof(OverflowException), ex.InnerException.GetType()); + Assert.NotNull(ex.Message); + Assert.True(ex.Message.IndexOf("'command timeout'") != -1); + Assert.Null(ex.ParamName); + + // Value was either too large or too small for an Int32 + OverflowException oe = (OverflowException)ex.InnerException; + Assert.Null(oe.InnerException); + Assert.NotNull(oe.Message); + } + } + [Fact] public void ConnectionString_Database_Synonyms() {