diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
index 3f1d2c4ccb..8e7e927187 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
@@ -1096,6 +1096,11 @@ GO
was called while the returned Task was not completed and the connection was not opened after a call to .
+
+ Gets the server process Id (SPID) of the active connection.
+ The server process Id (SPID) of the active connection.
+ Returns 0 if the connection is inactive on the client side.
+
Indicates the state of the during the most recent network operation performed on the connection.
An enumeration.
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 f871d68d4b..3c875eb511 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
@@ -560,7 +560,7 @@ internal string SQLDNSCachingSupportedState
if (null != innerConnection)
{
- result = innerConnection.IsSQLDNSCachingSupported ? "true": "false";
+ result = innerConnection.IsSQLDNSCachingSupported ? "true" : "false";
}
else
{
@@ -583,7 +583,7 @@ internal string SQLDNSCachingSupportedStateBeforeRedirect
if (null != innerConnection)
{
- result = innerConnection.IsDNSCachingBeforeRedirectSupported ? "true": "false";
+ result = innerConnection.IsDNSCachingBeforeRedirectSupported ? "true" : "false";
}
else
{
@@ -670,6 +670,13 @@ public override string ServerVersion
get => GetOpenTdsConnection().ServerVersion;
}
+ ///
+ public int ServerProcessId
+ {
+ get => State.Equals(ConnectionState.Open) | State.Equals(ConnectionState.Executing) | State.Equals(ConnectionState.Fetching) ?
+ GetOpenTdsConnection().ServerProcessId : 0;
+ }
+
///
public override ConnectionState State
{
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
index ccf7853165..67ad4668e8 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
@@ -675,6 +675,14 @@ public override string ServerVersion
}
}
+ public int ServerProcessId
+ {
+ get
+ {
+ return Parser._physicalStateObj._spid;
+ }
+ }
+
protected override bool UnbindOnTransactionCompletion
{
get
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs
index 043a70669d..1d2dd44c0a 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs
@@ -53,6 +53,7 @@ internal static class TdsEnums
// header constants
public const int HEADER_LEN = 8;
public const int HEADER_LEN_FIELD_OFFSET = 2;
+ public const int SPID_OFFSET = 4;
public const int YUKON_HEADER_LEN = 12; //Yukon headers also include a MARS session id
public const int MARS_ID_OFFSET = 8;
public const int HEADERTYPE_QNOTIFICATION = 1;
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
index 44eb698f48..b437802ce2 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
@@ -89,6 +89,8 @@ internal enum SnapshottedStateFlags : byte
///
internal int _inBytesPacket;
+ internal int _spid; // SPID of the current connection
+
// Packet state variables
internal byte _outputMessageType; // tds header type
internal byte _messageStatus; // tds header status
@@ -1019,6 +1021,8 @@ internal bool TryProcessHeader()
(int)_partialHeaderBuffer[TdsEnums.HEADER_LEN_FIELD_OFFSET + 1]) - _inputHeaderLen;
_messageStatus = _partialHeaderBuffer[1];
+ _spid = _partialHeaderBuffer[TdsEnums.SPID_OFFSET] << 8 |
+ _partialHeaderBuffer[TdsEnums.SPID_OFFSET + 1];
}
else
{
@@ -1052,8 +1056,10 @@ internal bool TryProcessHeader()
{
// normal header processing...
_messageStatus = _inBuff[_inBytesUsed + 1];
- _inBytesPacket = ((int)_inBuff[_inBytesUsed + TdsEnums.HEADER_LEN_FIELD_OFFSET] << 8 |
- (int)_inBuff[_inBytesUsed + TdsEnums.HEADER_LEN_FIELD_OFFSET + 1]) - _inputHeaderLen;
+ _inBytesPacket = (_inBuff[_inBytesUsed + TdsEnums.HEADER_LEN_FIELD_OFFSET] << 8 |
+ _inBuff[_inBytesUsed + TdsEnums.HEADER_LEN_FIELD_OFFSET + 1]) - _inputHeaderLen;
+ _spid = _inBuff[_inBytesUsed + TdsEnums.SPID_OFFSET] << 8 |
+ _inBuff[_inBytesUsed + TdsEnums.SPID_OFFSET + 1];
_inBytesUsed += _inputHeaderLen;
AssertValidState();
@@ -3481,35 +3487,35 @@ internal void SendAttention(bool mustTakeWriteLock = false)
if (!_skipSendAttention)
{
#endif
- // Take lock and send attention
- bool releaseLock = false;
- if ((mustTakeWriteLock) && (!_parser.Connection.ThreadHasParserLockForClose))
+ // Take lock and send attention
+ bool releaseLock = false;
+ if ((mustTakeWriteLock) && (!_parser.Connection.ThreadHasParserLockForClose))
+ {
+ releaseLock = true;
+ _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false);
+ _parser.Connection.ThreadHasParserLockForClose = true;
+ }
+ try
+ {
+ // Check again (just in case the connection was closed while we were waiting)
+ if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken)
{
- releaseLock = true;
- _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false);
- _parser.Connection.ThreadHasParserLockForClose = true;
+ return;
}
- try
- {
- // Check again (just in case the connection was closed while we were waiting)
- if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken)
- {
- return;
- }
- uint sniError;
- _parser._asyncWrite = false; // stop async write
- SNIWritePacket(attnPacket, out sniError, canAccumulate: false, callerHasConnectionLock: false);
- SqlClientEventSource.Log.TraceEvent(" Send Attention ASync.", "Info");
- }
- finally
+ uint sniError;
+ _parser._asyncWrite = false; // stop async write
+ SNIWritePacket(attnPacket, out sniError, canAccumulate: false, callerHasConnectionLock: false);
+ SqlClientEventSource.Log.TraceEvent(" Send Attention ASync.", "Info");
+ }
+ finally
+ {
+ if (releaseLock)
{
- if (releaseLock)
- {
- _parser.Connection.ThreadHasParserLockForClose = false;
- _parser.Connection._parserLock.Release();
- }
+ _parser.Connection.ThreadHasParserLockForClose = false;
+ _parser.Connection._parserLock.Release();
}
+ }
#if DEBUG
}
#endif
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
index f3b68d3da1..6e247cc897 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -128,7 +128,7 @@
Microsoft\Data\SqlClient\Server\ExtendedClrTypeCode.cs
-
+
Microsoft\Data\SqlClient\Server\IBinarySerialize.cs
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 d48d907e1c..f023b3dacb 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
@@ -738,7 +738,7 @@ internal string SQLDNSCachingSupportedState
if (null != innerConnection)
{
- result = innerConnection.IsSQLDNSCachingSupported ? "true": "false";
+ result = innerConnection.IsSQLDNSCachingSupported ? "true" : "false";
}
else
{
@@ -762,7 +762,7 @@ internal string SQLDNSCachingSupportedStateBeforeRedirect
if (null != innerConnection)
{
- result = innerConnection.IsDNSCachingBeforeRedirectSupported ? "true": "false";
+ result = innerConnection.IsDNSCachingBeforeRedirectSupported ? "true" : "false";
}
else
{
@@ -878,6 +878,18 @@ override public string ServerVersion
}
}
+ ///
+ [
+ Browsable(false),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
+ ResDescription(StringsHelper.ResourceNames.SqlConnection_ServerProcessId),
+ ]
+ public int ServerProcessId
+ {
+ get => State.Equals(ConnectionState.Open) | State.Equals(ConnectionState.Executing) | State.Equals(ConnectionState.Fetching) ?
+ GetOpenTdsConnection().ServerProcessId : 0;
+ }
+
///
[
Browsable(false),
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
index f677a1f37d..7b166cfe7f 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
@@ -788,6 +788,13 @@ override public string ServerVersion
(short)_loginAck.minorVersion, _loginAck.buildNum));
}
}
+ public int ServerProcessId
+ {
+ get
+ {
+ return Parser._physicalStateObj._spid;
+ }
+ }
///
/// Get boolean that specifies whether an enlisted transaction can be unbound from
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs
index a444991ddb..cf8df1524b 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs
@@ -67,6 +67,7 @@ internal static class TdsEnums
// header constants
public const int HEADER_LEN = 8;
public const int HEADER_LEN_FIELD_OFFSET = 2;
+ public const int SPID_OFFSET = 4;
public const int YUKON_HEADER_LEN = 12; //Yukon headers also include a MARS session id
public const int MARS_ID_OFFSET = 8;
public const int HEADERTYPE_QNOTIFICATION = 1;
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
index 09305c3cf4..62cc1b7be3 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
@@ -71,6 +71,8 @@ internal int ObjectID
internal int _inBytesUsed = 0; // number of bytes used in internal read buffer
internal int _inBytesRead = 0; // number of bytes read into internal read buffer
internal int _inBytesPacket = 0; // number of bytes left in packet
+
+ internal int _spid; // SPID of the current connection
// Packet state variables
internal byte _outputMessageType = 0; // tds header type
@@ -1131,6 +1133,8 @@ internal bool TryProcessHeader()
(int)_partialHeaderBuffer[TdsEnums.HEADER_LEN_FIELD_OFFSET + 1]) - _inputHeaderLen;
_messageStatus = _partialHeaderBuffer[1];
+ _spid = _partialHeaderBuffer[TdsEnums.SPID_OFFSET] << 8 |
+ _partialHeaderBuffer[TdsEnums.SPID_OFFSET + 1];
}
else
{
@@ -1166,8 +1170,10 @@ internal bool TryProcessHeader()
{
// normal header processing...
_messageStatus = _inBuff[_inBytesUsed + 1];
- _inBytesPacket = ((int)_inBuff[_inBytesUsed + TdsEnums.HEADER_LEN_FIELD_OFFSET] << 8 |
- (int)_inBuff[_inBytesUsed + TdsEnums.HEADER_LEN_FIELD_OFFSET + 1]) - _inputHeaderLen;
+ _inBytesPacket = (_inBuff[_inBytesUsed + TdsEnums.HEADER_LEN_FIELD_OFFSET] << 8 |
+ _inBuff[_inBytesUsed + TdsEnums.HEADER_LEN_FIELD_OFFSET + 1]) - _inputHeaderLen;
+ _spid = _inBuff[_inBytesUsed + TdsEnums.SPID_OFFSET] << 8 |
+ _inBuff[_inBytesUsed + TdsEnums.SPID_OFFSET + 1];
_inBytesUsed += _inputHeaderLen;
AssertValidState();
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
index a4ca42aeb4..b6b1a532df 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
@@ -8092,7 +8092,7 @@ internal class Strings {
}
///
- /// Looks up a localized string similar to Security Warning: The negotiated '{0}' is an insecured protocol and is supported for backward compatibility only. The recommended protocol is TLS 1.2 and later..
+ /// Looks up a localized string similar to Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later..
///
internal static string SEC_ProtocolWarning {
get {
@@ -10539,6 +10539,15 @@ internal class Strings {
}
}
+ ///
+ /// Looks up a localized string similar to Server Process Id (SPID) of the active connection..
+ ///
+ internal static string SqlConnection_ServerProcessId {
+ get {
+ return ResourceManager.GetString("SqlConnection_ServerProcessId", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Version of the SQL Server accessed by the SqlConnection..
///
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
index 3cc34302a4..1a90bdf991 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
@@ -4560,4 +4560,7 @@
Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
+
+ Server Process Id (SPID) of the active connection.
+
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs
index f4a1d37fe4..ba9777a432 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs
@@ -27,7 +27,7 @@ public static class ConnectivityTest
private static readonly string s_dropDatabaseCmd = $"DROP DATABASE {s_databaseName}";
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
- public static void EnvironmentHostNameTest()
+ public static void EnvironmentHostNameSPIDTest()
{
SqlConnectionStringBuilder builder = (new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = true });
builder.ApplicationName = "HostNameTest";
@@ -35,6 +35,7 @@ public static void EnvironmentHostNameTest()
using (SqlConnection sqlConnection = new SqlConnection(builder.ConnectionString))
{
sqlConnection.Open();
+ int sqlClientSPID = sqlConnection.ServerProcessId;
int sessionSpid;
using (SqlCommand cmd = new SqlCommand("SELECT @@SPID", sqlConnection))
@@ -43,6 +44,11 @@ public static void EnvironmentHostNameTest()
reader.Read();
sessionSpid = reader.GetInt16(0);
}
+ // Confirm Server process id is same as result of SELECT @@SPID
+ Assert.Equal(sessionSpid, sqlClientSPID);
+
+ // Confirm once again SPID on SqlConnection directly
+ Assert.Equal(sessionSpid, sqlConnection.ServerProcessId);
using (SqlCommand command = new SqlCommand("sp_who2", sqlConnection))
using (SqlDataReader reader = command.ExecuteReader())
@@ -55,7 +61,7 @@ public static void EnvironmentHostNameTest()
int spidOrdinal = reader.GetOrdinal(COL_SPID);
string spid = reader.GetString(spidOrdinal);
- if (programName != null && programName.Trim().Equals(builder.ApplicationName) && Int16.Parse(spid) == sessionSpid)
+ if (programName != null && programName.Trim().Equals(builder.ApplicationName) && short.Parse(spid) == sessionSpid)
{
// Get the hostname
int hostnameOrdinal = reader.GetOrdinal(COL_HOSTNAME);
@@ -67,6 +73,8 @@ public static void EnvironmentHostNameTest()
}
}
}
+ // Confirm Server Process Id stays the same after query execution
+ Assert.Equal(sessionSpid, sqlConnection.ServerProcessId);
}
Assert.True(false, "No non-empty hostname found for the application");
}
@@ -106,11 +114,11 @@ public static void ConnectionTimeoutTestWithThread()
}
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
- public static void ProcessIdTest()
+ public static void LocalProcessIdTest()
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString);
string sqlProviderName = builder.ApplicationName;
- string sqlProviderProcessID = System.Diagnostics.Process.GetCurrentProcess().Id.ToString();
+ string sqlProviderProcessID = Process.GetCurrentProcess().Id.ToString();
using (SqlConnection sqlConnection = new SqlConnection(builder.ConnectionString))
{
@@ -228,7 +236,7 @@ public static void ConnectionKilledTest()
}
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
- public static void ConnectionResiliencyTest()
+ public static void ConnectionResiliencySPIDTest()
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString);
builder.ConnectRetryCount = 0;
@@ -256,23 +264,37 @@ public static void ConnectionResiliencyTest()
}
builder.ConnectRetryCount = 2;
+ // Also check SPID changes with connection resiliency
using (SqlConnection conn = new SqlConnection(builder.ConnectionString))
{
conn.Open();
+ int clientSPID = conn.ServerProcessId;
+ int serverSPID = 0;
InternalConnectionWrapper wrapper = new InternalConnectionWrapper(conn, true, builder.ConnectionString);
using (SqlCommand cmd = conn.CreateCommand())
{
- cmd.CommandText = "SELECT TOP 1 * FROM dbo.Employees";
+ cmd.CommandText = "SELECT @@SPID";
using (SqlDataReader reader = cmd.ExecuteReader())
while (reader.Read())
- { }
+ {
+ serverSPID = reader.GetInt16(0);
+ }
+
+ Assert.Equal(serverSPID, clientSPID);
+ // Also check SPID after query execution
+ Assert.Equal(serverSPID, conn.ServerProcessId);
wrapper.KillConnectionByTSql();
// Connection resiliency should reconnect transparently
using (SqlDataReader reader = cmd.ExecuteReader())
while (reader.Read())
- { }
+ {
+ serverSPID = reader.GetInt16(0);
+ }
+
+ // SPID should match server's SPID
+ Assert.Equal(serverSPID, conn.ServerProcessId);
}
}
}