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 e2c6feedfa..be51c3e62c 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 @@ -563,7 +563,9 @@ public Guid ClientConnectionId else { Task reconnectTask = _currentReconnectionTask; - if (reconnectTask != null && !reconnectTask.IsCompleted) + // Connection closed but previously open should return the correct ClientConnectionId + DbConnectionClosedPreviouslyOpened innerConnectionClosed = (InnerConnection as DbConnectionClosedPreviouslyOpened); + if ((reconnectTask != null && !reconnectTask.IsCompleted) || null != innerConnectionClosed) { return _originalConnectionId; } @@ -856,6 +858,7 @@ private void CloseInnerConnection() // The SqlInternalConnectionTds is set to OpenBusy during close, once this happens the cast below will fail and // the command will no longer be cancelable. It might be desirable to be able to cancel the close operation, but this is // outside of the scope of Whidbey RTM. See (SqlCommand::Cancel) for other lock. + _originalConnectionId = ClientConnectionId; InnerConnection.CloseConnection(this, ConnectionFactory); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlStatistics.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlStatistics.cs index da71b7e1b7..32e06b2df0 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlStatistics.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlStatistics.cs @@ -210,9 +210,9 @@ internal long SafeIncrement(ref long value) internal void UpdateStatistics() { // update connection time - if (_closeTimestamp >= _openTimestamp) + if (_closeTimestamp >= _openTimestamp && long.MaxValue > _closeTimestamp - _openTimestamp) { - SafeAdd(ref _connectionTime, _closeTimestamp - _openTimestamp); + _connectionTime = _closeTimestamp - _openTimestamp; } else { 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 30e3feb101..f7b88667d6 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 @@ -792,7 +792,9 @@ public Guid ClientConnectionId else { Task reconnectTask = _currentReconnectionTask; - if (reconnectTask != null && !reconnectTask.IsCompleted) + // Connection closed but previously open should return the correct ClientConnectionId + DbConnectionClosedPreviouslyOpened innerConnectionClosed = (InnerConnection as DbConnectionClosedPreviouslyOpened); + if ((reconnectTask != null && !reconnectTask.IsCompleted) || null != innerConnectionClosed) { return _originalConnectionId; } @@ -1253,6 +1255,7 @@ void CloseInnerConnection() // The SqlInternalConnectionTds is set to OpenBusy during close, once this happens the cast below will fail and // the command will no longer be cancelable. It might be desirable to be able to cancel the close opperation, but this is // outside of the scope of Whidbey RTM. See (SqlCommand::Cancel) for other lock. + _originalConnectionId = ClientConnectionId; InnerConnection.CloseConnection(this, ConnectionFactory); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlStatistics.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlStatistics.cs index 036b1d99ce..28dfee86f0 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlStatistics.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlStatistics.cs @@ -207,9 +207,9 @@ internal long SafeIncrement(ref long value) internal void UpdateStatistics() { // update connection time - if (_closeTimestamp >= _openTimestamp) + if (_closeTimestamp >= _openTimestamp && long.MaxValue > _closeTimestamp - _openTimestamp) { - SafeAdd(ref _connectionTime, _closeTimestamp - _openTimestamp); + _connectionTime = _closeTimestamp - _openTimestamp; } else { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 7a82bfbf08..f20d1ddccb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -113,6 +113,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlStatisticsTest/SqlStatisticsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlStatisticsTest/SqlStatisticsTest.cs new file mode 100644 index 0000000000..53c71f5ced --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlStatisticsTest/SqlStatisticsTest.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Data; +using System.Data.Common; +using System.Collections; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + public class SqlStatisticsTest + { + private static DateTime startTime = new DateTime(); + private static Guid clientConnectionId = Guid.Empty; + + [CheckConnStrSetupFact] + public static void TestRetrieveStatistics() + { + startTime = DateTime.Now; + SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString); + string text = "SELECT TOP 2000 * from [dbo].[Order Details]"; + using (SqlCommand command = new SqlCommand(text)) + { + connection.StatisticsEnabled = true; + connection.Open(); + connection.StateChange += new StateChangeEventHandler(HandleStateChange); + + command.Connection = connection; + using (SqlDataReader dr = command.ExecuteReader()) + { + IDictionary stats1 = connection.RetrieveStatistics(); + + // Ensure ConnectionTime is within a reasonable range + Assert.True((long)stats1["ConnectionTime"] < DateTime.Now.Subtract(startTime).TotalMilliseconds + 1000, "Unexpected ConnectionTime: " + stats1["ConnectionTime"]); + clientConnectionId = connection.ClientConnectionId; + Assert.True(clientConnectionId != Guid.Empty); + + int row = 0; + while (dr.Read()) + { + row++; + } + } + } + // Ensure calling RetrieveStatistics multiple times do not affect the ConnectionTime + connection.RetrieveStatistics(); + connection.RetrieveStatistics(); + connection.RetrieveStatistics(); + connection.RetrieveStatistics(); + connection.RetrieveStatistics(); + connection.RetrieveStatistics(); + connection.Close(); + IDictionary stats2 = connection.RetrieveStatistics(); + Assert.True((long)stats2["ConnectionTime"] < DateTime.Now.Subtract(startTime).TotalMilliseconds + 1000, "Unexpected ConnectionTime: " + stats2["ConnectionTime"]); + // Ensure ClientConnectionId remains available even after the connection is closed + Assert.True(connection.ClientConnectionId == clientConnectionId); + } + + private static void HandleStateChange(object sender, StateChangeEventArgs args) + { + if (args.CurrentState == ConnectionState.Closed) + { + System.Collections.IDictionary stats = ((SqlConnection)sender).RetrieveStatistics(); + Assert.True((long)stats["ConnectionTime"] < DateTime.Now.Subtract(startTime).TotalMilliseconds + 1000, "Unexpected ConnectionTime: " + stats["ConnectionTime"]); + Assert.True(((SqlConnection)sender).ClientConnectionId == clientConnectionId); + } + } + } +}