Skip to content

Commit

Permalink
Add SqlConnectionOverrides for a fast Open() option. (#463)
Browse files Browse the repository at this point in the history
  • Loading branch information
David Engel authored Apr 21, 2020
1 parent 5ceec82 commit 7d60b0d
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 9 deletions.
40 changes: 40 additions & 0 deletions doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,46 @@ GO
The <see langword="&lt;system.data.localdb&gt;" /> tag in the app.config file has invalid or unknown elements.</exception>
<exception cref="T:System.Configuration.ConfigurationErrorsException">There are two entries with the same name in the <see langword="&lt;localdbinstances&gt;" /> section.</exception>
</Open>
<OpenWithOverrides>
<param name="overrides">Options to override default connection open behavior.</param>
<summary>
Opens a database connection with the property settings specified by the <see cref="P:Microsoft.Data.SqlClient.SqlConnection.ConnectionString" />.
</summary>
<remarks>
<format type="text/markdown">
<![CDATA[
## Remarks
The <xref:Microsoft.Data.SqlClient.SqlConnection> draws an open connection from the connection pool if one is available. Otherwise, it establishes a new connection to an instance of SQL Server. If overrides are specified, the first open attempt will apply the specified overrides to the open action.
> [!NOTE]
> If the <xref:Microsoft.Data.SqlClient.SqlConnection> goes out of scope, it is not closed. Therefore, you must explicitly close the connection by calling <xref:Microsoft.Data.SqlClient.SqlConnection.Close%2A>.
> [!NOTE]
> If you specify a port number other than 1433 when you are trying to connect to an instance of SQL Server and using a protocol other than TCP/IP, the <xref:Microsoft.Data.SqlClient.SqlConnection.Open%2A> method fails. To specify a port number other than 1433, include "server=machinename,port number" in the connection string, and use the TCP/IP protocol.
> [!NOTE]
> The .NET Framework Data Provider for SQL Server requires the Security permission with "Allows calls to unmanaged assemblies" enabled (<xref:System.Security.Permissions.SecurityPermission> with <xref:System.Security.Permissions.SecurityPermissionFlag> set to `UnmanagedCode`) to open a <xref:Microsoft.Data.SqlClient.SqlConnection> with SQL Debugging enabled.
## Examples
The following example creates a <xref:Microsoft.Data.SqlClient.SqlConnection>, opens it, and displays some of its properties. The connection is automatically closed at the end of the `using` block.
[!code-csharp[SqlConnection_Open Example#1](~/../sqlclient/doc/samples/SqlConnection_Open.cs#1)]
]]>
</format>
</remarks>
<exception cref="T:System.InvalidOperationException">Cannot open a connection without specifying a data source or server.

or

The connection is already open.
</exception>
<exception cref="T:Microsoft.Data.SqlClient.SqlException">A connection-level error occurred while opening the connection. If the <see cref="P:Microsoft.Data.SqlClient.SqlException.Number" /> property contains the value 18487 or 18488, this indicates that the specified password has expired or must be reset. See the <see cref="M:Microsoft.Data.SqlClient.SqlConnection.ChangePassword(System.String,System.String)" /> method for more information.

The <see langword="&lt;system.data.localdb&gt;" /> tag in the app.config file has invalid or unknown elements.</exception>
<exception cref="T:System.Configuration.ConfigurationErrorsException">There are two entries with the same name in the <see langword="&lt;localdbinstances&gt;" /> section.</exception>
</OpenWithOverrides>
<OpenAsync>
<param name="cancellationToken">The cancellation instruction.</param>
<summary>An asynchronous version of <see cref="M:Microsoft.Data.SqlClient.SqlConnection.Open" />, which opens a database connection with the property settings specified by the <see cref="P:Microsoft.Data.SqlClient.SqlConnection.ConnectionString" />. The cancellation token can be used to request that the operation be abandoned before the connection timeout elapses. Exceptions will be propagated via the returned Task. If the connection timeout time elapses without successfully connecting, the returned Task will be marked as faulted with an Exception. The implementation returns a Task without blocking the calling thread for both pooled and non-pooled connections.</summary>
Expand Down
17 changes: 17 additions & 0 deletions doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<docs>
<members name="SqlConnectionOverrides">
<SqlConnectionOverrides>
<summary>
Specifies a value for Overrides.
</summary>
</SqlConnectionOverrides>
<None>
<summary>No overrides.</summary>
<value>0</value>
</None>
<OpenWithoutRetry>
<summary>Disable transient fault handling during the initial SqlConnection Open attempt.</summary>
<value>1</value>
</OpenWithoutRetry>
</members>
</docs>
Original file line number Diff line number Diff line change
Expand Up @@ -591,13 +591,23 @@ public event Microsoft.Data.SqlClient.SqlInfoMessageEventHandler InfoMessage { a
public override System.Data.DataTable GetSchema(string collectionName, string[] restrictionValues) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/Open/*'/>
public override void Open() { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/OpenWithOverrides/*'/>
public void Open(SqlConnectionOverrides overrides) { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/OpenAsync/*'/>
public override System.Threading.Tasks.Task OpenAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/ResetStatistics/*'/>
public void ResetStatistics() { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/RetrieveStatistics/*'/>
public System.Collections.IDictionary RetrieveStatistics() { throw null; }
}
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/SqlConnectionOverrides/*' />
public enum SqlConnectionOverrides
{
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/None/*' />
None = 0,
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/OpenWithoutRetry/*' />
OpenWithoutRetry = 1,
}
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/SqlConnectionStringBuilder/*'/>
[System.ComponentModel.DefaultPropertyAttribute("DataSource")]
public sealed partial class SqlConnectionStringBuilder : System.Data.Common.DbConnectionStringBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,12 @@ private void DisposeMe(bool disposing)

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/Open/*' />
public override void Open()
{
Open(SqlConnectionOverrides.None);
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/OpenWithOverrides/*' />
public void Open(SqlConnectionOverrides overrides)
{
long scopeID = SqlClientEventSource.Log.ScopeEnterEvent("<sc.SqlConnection.Open|API|Correlation> ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current);
SqlClientEventSource.Log.CorrelationTraceEvent("<sc.SqlConnection.Open|API|Correlation> ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current);
Expand All @@ -1014,7 +1020,7 @@ public override void Open()
{
statistics = SqlStatistics.StartTimer(Statistics);

if (!TryOpen(null))
if (!TryOpen(null, overrides))
{
throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending);
}
Expand Down Expand Up @@ -1469,7 +1475,7 @@ private void PrepareStatisticsForNewConnection()
}
}

private bool TryOpen(TaskCompletionSource<DbConnectionInternal> retry)
private bool TryOpen(TaskCompletionSource<DbConnectionInternal> retry, SqlConnectionOverrides overrides = SqlConnectionOverrides.None)
{
SqlConnectionString connectionOptions = (SqlConnectionString)ConnectionOptions;

Expand All @@ -1489,7 +1495,7 @@ private bool TryOpen(TaskCompletionSource<DbConnectionInternal> retry)
}
}

_applyTransientFaultHandling = (retry == null && connectionOptions != null && connectionOptions.ConnectRetryCount > 0);
_applyTransientFaultHandling = (!overrides.HasFlag(SqlConnectionOverrides.OpenWithoutRetry) && retry == null && connectionOptions != null && connectionOptions.ConnectRetryCount > 0);

if (connectionOptions != null &&
(connectionOptions.Authentication == SqlAuthenticationMethod.SqlPassword || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword) &&
Expand All @@ -1515,6 +1521,9 @@ private bool TryOpen(TaskCompletionSource<DbConnectionInternal> retry)
}
// does not require GC.KeepAlive(this) because of ReRegisterForFinalize below.

// Set future transient fault handling based on connection options
_applyTransientFaultHandling = (retry == null && connectionOptions != null && connectionOptions.ConnectRetryCount > 0);

var tdsInnerConnection = (SqlInternalConnectionTds)InnerConnection;

Debug.Assert(tdsInnerConnection.Parser != null, "Where's the parser?");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,16 @@ public enum SqlConnectionColumnEncryptionSetting
Enabled,
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/SqlConnectionOverrides/*' />
[Flags]
public enum SqlConnectionOverrides
{
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/None/*' />
None = 0,
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/OpenWithoutRetry/*' />
OpenWithoutRetry = 1,
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlCommandColumnEncryptionSetting.xml' path='docs/members[@name="SqlCommandColumnEncryptionSetting"]/SqlCommandColumnEncryptionSetting/*' />
public enum SqlCommandColumnEncryptionSetting
{
Expand Down
11 changes: 11 additions & 0 deletions src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,8 @@ public override void EnlistTransaction(System.Transactions.Transaction transacti
public override System.Data.DataTable GetSchema(string collectionName, string[] restrictionValues) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/Open/*'/>
public override void Open() { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/OpenWithOverrides/*'/>
public void Open(SqlConnectionOverrides overrides) { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/OpenAsync/*'/>
public override System.Threading.Tasks.Task OpenAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/RegisterColumnEncryptionKeyStoreProviders/*'/>
Expand Down Expand Up @@ -813,6 +815,15 @@ public enum SqlConnectionAttestationProtocol
HGS = 3
}

/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/SqlConnectionOverrides/*' />
public enum SqlConnectionOverrides
{
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/None/*' />
None = 0,
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/OpenWithoutRetry/*' />
OpenWithoutRetry = 1,
}

/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/SqlConnectionStringBuilder/*'/>
[System.ComponentModel.DefaultPropertyAttribute("DataSource")]
public sealed partial class SqlConnectionStringBuilder : System.Data.Common.DbConnectionStringBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,12 @@ public void EnlistDistributedTransaction(System.EnterpriseServices.ITransaction

/// <include file='..\..\..\..\..\..\..\doc\snippets\Microsoft.Data.SqlClient\SqlConnection.xml' path='docs/members[@name="SqlConnection"]/Open/*' />
override public void Open()
{
Open(SqlConnectionOverrides.None);
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/OpenWithOverrides/*' />
public void Open(SqlConnectionOverrides overrides)
{
long scopeID = SqlClientEventSource.Log.ScopeEnterEvent("<sc.SqlConnection.Open|API|Correlation> ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current);
SqlClientEventSource.Log.CorrelationTraceEvent("<sc.SqlConnection.Open|API|Correlation> ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current);
Expand All @@ -1420,7 +1426,7 @@ override public void Open()
{
statistics = SqlStatistics.StartTimer(Statistics);

if (!TryOpen(null))
if (!TryOpen(null, overrides))
{
throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending);
}
Expand Down Expand Up @@ -1814,10 +1820,13 @@ internal void Retry(Task<DbConnectionInternal> retryTask)
}
}

private bool TryOpen(TaskCompletionSource<DbConnectionInternal> retry)
private bool TryOpen(TaskCompletionSource<DbConnectionInternal> retry, SqlConnectionOverrides overrides = SqlConnectionOverrides.None)
{
SqlConnectionString connectionOptions = (SqlConnectionString)ConnectionOptions;
_applyTransientFaultHandling = (retry == null && connectionOptions != null && connectionOptions.ConnectRetryCount > 0);

bool result = false;

_applyTransientFaultHandling = (!overrides.HasFlag(SqlConnectionOverrides.OpenWithoutRetry) && retry == null && connectionOptions != null && connectionOptions.ConnectRetryCount > 0);

if (connectionOptions != null &&
(connectionOptions.Authentication == SqlAuthenticationMethod.SqlPassword || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword) &&
Expand All @@ -1833,13 +1842,13 @@ private bool TryOpen(TaskCompletionSource<DbConnectionInternal> retry)
{
if (_impersonateIdentity.User == identity.User)
{
return TryOpenInner(retry);
result = TryOpenInner(retry);
}
else
{
using (WindowsImpersonationContext context = _impersonateIdentity.Impersonate())
{
return TryOpenInner(retry);
result = TryOpenInner(retry);
}
}
}
Expand All @@ -1854,8 +1863,13 @@ private bool TryOpen(TaskCompletionSource<DbConnectionInternal> retry)
{
_lastIdentity = null;
}
return TryOpenInner(retry);
result = TryOpenInner(retry);
}

// Set future transient fault handling based on connection options
_applyTransientFaultHandling = (retry == null && connectionOptions != null && connectionOptions.ConnectRetryCount > 0);

return result;
}

private bool TryOpenInner(TaskCompletionSource<DbConnectionInternal> retry)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,16 @@ public enum SqlConnectionColumnEncryptionSetting
Enabled,
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/SqlConnectionOverrides/*' />
[Flags]
public enum SqlConnectionOverrides
{
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/None/*' />
None = 0,
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/OpenWithoutRetry/*' />
OpenWithoutRetry = 1,
}

/// <include file='..\..\..\..\..\..\..\doc\snippets\Microsoft.Data.SqlClient\SqlCommandColumnEncryptionSetting.xml' path='docs/members[@name="SqlCommandColumnEncryptionSetting"]/SqlCommandColumnEncryptionSetting/*' />
public enum SqlCommandColumnEncryptionSetting
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,5 +304,38 @@ public static void ConnectionStringPersistentInfoTest()
Assert.True(connectionStringBuilder.Password != string.Empty, "Password must persist according to set the PersistSecurityInfo by true!");
}
}

[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
public static void ConnectionOpenDisableRetry()
{
using (SqlConnection sqlConnection = new SqlConnection((new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { InitialCatalog = "DoesNotExist0982532435423", Pooling = false }).ConnectionString))
{
TimeSpan duration;
DateTime start = DateTime.Now;
try
{
sqlConnection.Open(SqlConnectionOverrides.OpenWithoutRetry);
Assert.True(false, "Connection succeeded to database that should not exist.");
}
catch (SqlException)
{
duration = DateTime.Now - start;
Assert.True(duration.TotalSeconds < 2, $"Connection Open() without retries took longer than expected. Expected < 2 sec. Took {duration.TotalSeconds} sec.");
}

start = DateTime.Now;
try
{
sqlConnection.Open();
Assert.True(false, "Connection succeeded to database that should not exist.");
}
catch (SqlException)
{
duration = DateTime.Now - start;
//Should not fail fast due to transient fault handling when DB does not exist
Assert.True(duration.TotalSeconds > 5, $"Connection Open() with retries took less time than expected. Expect > 5 sec with transient fault handling. Took {duration.TotalSeconds} sec.");
}
}
}
}
}

0 comments on commit 7d60b0d

Please sign in to comment.