diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index ab2d25908c..a4b417b002 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -268,6 +268,9 @@
Microsoft\Data\SqlClient\SqlDependency.cs
+
+ Microsoft\Data\SqlClient\SqlDependencyListener.cs
+
Microsoft\Data\SqlClient\SqlEnums.cs
@@ -554,7 +557,6 @@
Microsoft\Data\SqlClient\SqlConnectionTimeoutErrorInternal.cs
-
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 87162db2ab..e3f9e789e3 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -351,6 +351,9 @@
Microsoft\Data\SqlClient\SqlDependency.cs
+
+ Microsoft\Data\SqlClient\SqlDependencyListener.cs
+
Microsoft\Data\SqlClient\SqlEnclaveAttestationParameters.Crypto.cs
@@ -548,7 +551,6 @@
-
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs
deleted file mode 100644
index 23dba04f58..0000000000
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs
+++ /dev/null
@@ -1,1870 +0,0 @@
-// 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.Collections.Generic;
-using System.Data;
-using System.Data.SqlTypes;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-using System.Runtime.Versioning;
-using System.Security.Principal;
-using System.Threading;
-using System.Xml;
-using Microsoft.Data;
-using Microsoft.Data.Common;
-using Microsoft.Data.ProviderBase;
-using Microsoft.Data.SqlClient;
-
-// This class is the process wide dependency dispatcher. It contains all connection listeners for the entire process and
-// receives notifications on those connections to dispatch to the corresponding AppDomain dispatcher to notify the
-// appropriate dependencies.
-
-// NOTE - a reference to this class is stored in native code - PROCESS WIDE STATE.
-
-internal class SqlDependencyProcessDispatcher : MarshalByRefObject
-{ // MBR since ref'ed by other appdomains.
-
- // -----------------------------------------------------------------------------------------------
- // Class to contain/store all relevant information about a connection that waits on the SSB queue.
- // -----------------------------------------------------------------------------------------------
- private class SqlConnectionContainer
- {
-
- private SqlConnection _con;
- private SqlCommand _com;
- private SqlParameter _conversationGuidParam;
- private SqlParameter _timeoutParam;
- private SqlConnectionContainerHashHelper _hashHelper;
- private WindowsIdentity _windowsIdentity;
- private string _queue;
- private string _receiveQuery;
- private string _beginConversationQuery;
- private string _endConversationQuery;
- private string _concatQuery;
- private readonly int _defaultWaitforTimeout = 60000; // Waitfor(Receive) timeout (milleseconds)
- private string _escapedQueueName;
- private string _sprocName;
- private string _dialogHandle;
- private string _cachedServer;
- private string _cachedDatabase;
- private volatile bool _errorState = false;
- private volatile bool _stop = false; // Can probably simplify this slightly - one bool instead of two.
- private volatile bool _stopped = false;
- private volatile bool _serviceQueueCreated = false;
- private int _startCount = 0; // Each container class is called once per Start() - we refCount
- // to track when we can dispose.
- private Timer _retryTimer = null;
- private Dictionary _appDomainKeyHash = null; // AppDomainKey->Open RefCount
-
- // -----------
- // BID members
- // -----------
-
- private readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
- private static int _objectTypeCount; // EventSource Counter
- internal int ObjectID
- {
- get
- {
- return _objectID;
- }
- }
-
- // -----------
- // Constructor
- // -----------
- internal SqlConnectionContainer(SqlConnectionContainerHashHelper hashHelper, string appDomainKey, bool useDefaults)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, queue: '{1}'", ObjectID, HashHelper?.Queue);
- bool setupCompleted = false;
-
- try
- {
- _hashHelper = hashHelper;
- string guid = null;
-
- // If default, queue name is not present on hashHelper at this point - so we need to
- // generate one and complete initialization.
- if (useDefaults)
- {
- guid = Guid.NewGuid().ToString();
- _queue = SQL.SqlNotificationServiceDefault + "-" + guid;
- _hashHelper.ConnectionStringBuilder.ApplicationName = _queue; // Used by cleanup sproc.
- }
- else
- {
- _queue = _hashHelper.Queue;
- }
-
-#if DEBUG
- SqlConnectionString connectionStringOptions = new SqlConnectionString(_hashHelper.ConnectionStringBuilder.ConnectionString);
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Modified connection string: '{0}'", connectionStringOptions.UsersConnectionStringForTrace());
-#endif
-
- // Always use ConnectionStringBuilder since in default case it is different from the
- // connection string used in the hashHelper.
- _con = new SqlConnection(_hashHelper.ConnectionStringBuilder.ConnectionString); // Create connection and open.
-
- // Assert permission for this particular connection string since it differs from the user passed string
- // which we have already demanded upon.
- SqlConnectionString connStringObj = (SqlConnectionString)_con.ConnectionOptions;
- connStringObj.CreatePermissionSet().Assert();
- if (connStringObj.LocalDBInstance != null)
- {
- // If it is LocalDB, we demanded LocalDB permissions too
- LocalDBAPI.AssertLocalDBPermissions();
- }
- _con.Open();
-
- _cachedServer = _con.DataSource; // SQL BU DT 390531.
-
- if (!_con.IsYukonOrNewer)
- { // After open, verify Yukon or later.
- throw SQL.NotificationsRequireYukon();
- }
-
- if (hashHelper.Identity != null)
- {
- // For now, DbConnectionPoolIdentity does not cache WindowsIdentity.
- // That means for every container creation, we create a WindowsIdentity twice.
- // We may want to improve this.
- _windowsIdentity = DbConnectionPoolIdentity.GetCurrentWindowsIdentity();
- }
-
- _escapedQueueName = SqlConnection.FixupDatabaseTransactionName(_queue); // Properly escape to prevent SQL Injection.
- _appDomainKeyHash = new Dictionary(); // Dictionary stores the Start/Stop refcount per AppDomain for this container.
- _com = new SqlCommand();
- _com.Connection = _con;
-
- // SQL BU DT 391534 - determine if broker is enabled on current database.
- _com.CommandText = "select is_broker_enabled from sys.databases where database_id=db_id()";
-
- if (!(bool)_com.ExecuteScalar())
- {
- throw SQL.SqlDependencyDatabaseBrokerDisabled();
- }
-
- _conversationGuidParam = new SqlParameter("@p1", SqlDbType.UniqueIdentifier);
- _timeoutParam = new SqlParameter("@p2", SqlDbType.Int);
- _timeoutParam.Value = 0; // Timeout set to 0 for initial sync query.
- _com.Parameters.Add(_timeoutParam);
-
- setupCompleted = true;
- // connection with the server has been setup - from this point use TearDownAndDispose() in case of error
-
- // Create standard query.
- _receiveQuery = "WAITFOR(RECEIVE TOP (1) message_type_name, conversation_handle, cast(message_body AS XML) as message_body from " + _escapedQueueName + "), TIMEOUT @p2;";
-
- // Create queue, service, sync query, and async query on user thread to ensure proper
- // init prior to return.
-
- if (useDefaults)
- { // Only create if user did not specify service & database.
- _sprocName = SqlConnection.FixupDatabaseTransactionName(SQL.SqlNotificationStoredProcedureDefault + "-" + guid);
- CreateQueueAndService(false); // Fail if we cannot create service, queue, etc.
- }
- else
- {
- // Continue query setup.
- _com.CommandText = _receiveQuery;
- _endConversationQuery = "END CONVERSATION @p1; ";
- _concatQuery = _endConversationQuery + _receiveQuery;
- }
-
- bool ignored = false;
- IncrementStartCount(appDomainKey, out ignored);
- // Query synchronously once to ensure everything is working correctly.
- // We want the exception to occur on start to immediately inform caller.
- SynchronouslyQueryServiceBrokerQueue();
- _timeoutParam.Value = _defaultWaitforTimeout; // Sync successful, extend timeout to 60 seconds.
- AsynchronouslyQueryServiceBrokerQueue();
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
-
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now.
- if (setupCompleted)
- {
- // Be sure to drop service & queue. This may fail if create service & queue failed.
- // This method will not drop unless we created or service & queue ref-count is 0.
- TearDownAndDispose();
- }
- else
- {
- // connection has not been fully setup yet - cannot use TearDownAndDispose();
- // we have to dispose the command and the connection to avoid connection leaks (until GC collects them).
- if (_com != null)
- {
- _com.Dispose();
- _com = null;
- }
- if (_con != null)
- {
- _con.Dispose();
- _con = null;
- }
-
- }
- throw;
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- // ----------
- // Properties
- // ----------
-
- internal string Database
- {
- get
- {
- if (_cachedDatabase == null)
- {
- _cachedDatabase = _con.Database;
- }
- return _cachedDatabase;
- }
- }
-
- internal SqlConnectionContainerHashHelper HashHelper
- {
- get
- {
- return _hashHelper;
- }
- }
-
- internal bool InErrorState
- {
- get
- {
- return _errorState;
- }
- }
-
- internal string Queue
- {
- get
- {
- return _queue;
- }
- }
-
- internal string Server
- {
- get
- {
- return _cachedServer;
- }
- }
-
- // -------
- // Methods
- // -------
-
- // This function is called by a ThreadPool thread as a result of an AppDomain calling
- // SqlDependencyProcessDispatcher.QueueAppDomainUnload on AppDomain.Unload.
- internal bool AppDomainUnload(string appDomainKey)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, AppDomainKey: '{1}'", ObjectID, appDomainKey);
- try
- {
- Debug.Assert(!ADP.IsEmpty(appDomainKey), "Unexpected empty appDomainKey!");
-
- // Dictionary used to track how many times start has been called per app domain.
- // For each decrement, subtract from count, and delete if we reach 0.
- lock (_appDomainKeyHash)
- {
- if (_appDomainKeyHash.ContainsKey(appDomainKey))
- {
- // Do nothing if AppDomain did not call Start!
- SqlClientEventSource.Log.TryNotificationTraceEvent(" _appDomainKeyHash contained AppDomainKey: '{0}'.", appDomainKey);
- int value = _appDomainKeyHash[appDomainKey];
- SqlClientEventSource.Log.TryNotificationTraceEvent("SqlConnectionContainer.AppDomainUnload|DEP> _appDomainKeyHash for AppDomainKey: '{0}' count: '{1}'.", appDomainKey, value);
- Debug.Assert(value > 0, "Why is value 0 or less?");
-
- bool ignored = false;
- while (value > 0)
- {
- Debug.Assert(!_stopped, "We should not yet be stopped!");
- Stop(appDomainKey, out ignored); // Stop will decrement value and remove if necessary from _appDomainKeyHash.
- value--;
- }
-
- // Stop will remove key when decremented to 0 for this AppDomain, which should now be the case.
- Debug.Assert(0 == value, "We did not reach 0 at end of loop in AppDomainUnload!");
- Debug.Assert(!_appDomainKeyHash.ContainsKey(appDomainKey), "Key not removed after AppDomainUnload!");
-
- if (_appDomainKeyHash.ContainsKey(appDomainKey))
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent("SqlConnectionContainer.AppDomainUnload|DEP|ERR> ERROR - after the Stop() loop, _appDomainKeyHash for AppDomainKey: '{0}' entry not removed from hash. Count: {1}'", appDomainKey, _appDomainKeyHash[appDomainKey]);
- }
- }
- else
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent("SqlConnectionContainer.AppDomainUnload|DEP> _appDomainKeyHash did not contain AppDomainKey: '{0}'.", appDomainKey);
- }
- }
- SqlClientEventSource.Log.TryNotificationTraceEvent("SqlConnectionContainer.AppDomainUnload|DEP> Exiting, _stopped: '{0}'.", _stopped);
- return _stopped;
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- private void AsynchronouslyQueryServiceBrokerQueue()
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
- try
- {
- AsyncCallback callback = new AsyncCallback(AsyncResultCallback);
- _com.BeginExecuteReader(callback, null); // NO LOCK NEEDED
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- private void AsyncResultCallback(IAsyncResult asyncResult)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
- try
- {
- using (SqlDataReader reader = _com.EndExecuteReader(asyncResult))
- {
- ProcessNotificationResults(reader);
- }
-
- // Successfull completion of query - no errors.
- if (!_stop)
- {
- AsynchronouslyQueryServiceBrokerQueue(); // Requeue...
- }
- else
- {
- TearDownAndDispose();
- }
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- // VSDD 590625: let the waiting thread detect the error and exit (otherwise, the Stop call loops forever)
- _errorState = true;
- throw;
- }
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Exception occurred.");
-
- if (!_stop)
- { // Only assert if not in cancel path.
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now.
- }
-
- // Failure - likely due to cancelled command. Check _stop state.
- if (_stop)
- {
- TearDownAndDispose();
- }
- else
- {
- _errorState = true;
- Restart(null); // Error code path. Will Invalidate based on server if 1st retry fails.
- }
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- private void CreateQueueAndService(bool restart)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
- try
- {
- SqlCommand com = new SqlCommand();
- com.Connection = _con;
- SqlTransaction trans = null;
-
- try
- {
- trans = _con.BeginTransaction(); // Since we cannot batch proc creation, start transaction.
- com.Transaction = trans;
-
- string nameLiteral = SqlServerEscapeHelper.MakeStringLiteral(_queue);
-
- com.CommandText =
- "CREATE PROCEDURE " + _sprocName + " AS"
- + " BEGIN"
- + " BEGIN TRANSACTION;"
- + " RECEIVE TOP(0) conversation_handle FROM " + _escapedQueueName + ";"
- + " IF (SELECT COUNT(*) FROM " + _escapedQueueName + " WHERE message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer') > 0"
- + " BEGIN"
- + " if ((SELECT COUNT(*) FROM sys.services WHERE name = " + nameLiteral + ") > 0)"
- + " DROP SERVICE " + _escapedQueueName + ";"
- + " if (OBJECT_ID(" + nameLiteral + ", 'SQ') IS NOT NULL)"
- + " DROP QUEUE " + _escapedQueueName + ";"
- + " DROP PROCEDURE " + _sprocName + ";" // Don't need conditional because this is self
- + " END"
- + " COMMIT TRANSACTION;"
- + " END";
-
- if (!restart)
- {
- com.ExecuteNonQuery();
- }
- else
- { // Upon restart, be resilient to the user dropping queue, service, or procedure.
- try
- {
- com.ExecuteNonQuery(); // Cannot add 'IF OBJECT_ID' to create procedure query - wrap and discard failure.
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e);
-
- try
- { // Since the failure will result in a rollback, rollback our object.
- if (null != trans)
- {
- trans.Rollback();
- trans = null;
- }
- }
- catch (Exception f)
- {
- if (!ADP.IsCatchableExceptionType(f))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(f); // Discard failure, but trace for now.
- }
- }
-
- if (null == trans)
- { // Create a new transaction for next operations.
- trans = _con.BeginTransaction();
- com.Transaction = trans;
- }
- }
-
-
- com.CommandText =
- "IF OBJECT_ID(" + nameLiteral + ", 'SQ') IS NULL"
- + " BEGIN"
- + " CREATE QUEUE " + _escapedQueueName + " WITH ACTIVATION (PROCEDURE_NAME=" + _sprocName + ", MAX_QUEUE_READERS=1, EXECUTE AS OWNER);"
- + " END;"
- + " IF (SELECT COUNT(*) FROM sys.services WHERE NAME=" + nameLiteral + ") = 0"
- + " BEGIN"
- + " CREATE SERVICE " + _escapedQueueName + " ON QUEUE " + _escapedQueueName + " ([http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification]);"
- + " IF (SELECT COUNT(*) FROM sys.database_principals WHERE name='sql_dependency_subscriber' AND type='R') <> 0"
- + " BEGIN"
- + " GRANT SEND ON SERVICE::" + _escapedQueueName + " TO sql_dependency_subscriber;"
- + " END; "
- + " END;"
- + " BEGIN DIALOG @dialog_handle FROM SERVICE " + _escapedQueueName + " TO SERVICE " + nameLiteral;
-
- SqlParameter param = new SqlParameter();
- param.ParameterName = "@dialog_handle";
- param.DbType = DbType.Guid;
- param.Direction = ParameterDirection.Output;
- com.Parameters.Add(param);
- com.ExecuteNonQuery();
-
- // Finish setting up queries and state. For re-start, we need to ensure we begin a new dialog above and reset
- // our queries to use the new dialogHandle.
- _dialogHandle = ((Guid)param.Value).ToString();
- _beginConversationQuery = "BEGIN CONVERSATION TIMER ('" + _dialogHandle + "') TIMEOUT = 120; " + _receiveQuery;
- _com.CommandText = _beginConversationQuery;
- _endConversationQuery = "END CONVERSATION @p1; ";
- _concatQuery = _endConversationQuery + _com.CommandText;
-
- trans.Commit();
- trans = null;
- _serviceQueueCreated = true;
- }
- finally
- {
- if (null != trans)
- {
- try
- {
- trans.Rollback();
- trans = null;
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now.
- }
- }
- }
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- internal void IncrementStartCount(string appDomainKey, out bool appDomainStart)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
- try
- {
- appDomainStart = false; // Reset out param.
- int result = Interlocked.Increment(ref _startCount); // Add to refCount.
- SqlClientEventSource.Log.TryNotificationTraceEvent("SqlConnectionContainer.IncrementStartCount|DEP> {0}, incremented _startCount: {1}", _staticInstance.ObjectID, result);
-
- // Dictionary used to track how many times start has been called per app domain.
- // For each increment, add to count, and create entry if not present.
- lock (_appDomainKeyHash)
- {
- if (_appDomainKeyHash.ContainsKey(appDomainKey))
- {
- _appDomainKeyHash[appDomainKey] = _appDomainKeyHash[appDomainKey] + 1;
- SqlClientEventSource.Log.TryNotificationTraceEvent("SqlConnectionContainer.IncrementStartCount|DEP> _appDomainKeyHash contained AppDomainKey: '{0}', incremented count: '{1}'.", appDomainKey, _appDomainKeyHash[appDomainKey]);
- }
- else
- {
- _appDomainKeyHash[appDomainKey] = 1;
- appDomainStart = true;
- SqlClientEventSource.Log.TryNotificationTraceEvent(" _appDomainKeyHash did not contain AppDomainKey: '{0}', added to hashtable and value set to 1.", appDomainKey);
- }
- }
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- private void ProcessNotificationResults(SqlDataReader reader)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
- try
- {
- Guid handle = Guid.Empty; // Conversation_handle. Always close this!
- try
- {
- if (!_stop)
- {
- while (reader.Read())
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Row read.");
-#if DEBUG
- if (SqlClientEventSource.Log.IsNotificationTraceEnabled())
- {
- for (int i = 0; i < reader.FieldCount; i++)
- {
- SqlClientEventSource.Log.NotificationTraceEvent(" column: {0}, value: {1}", reader.GetName(i), reader.GetValue(i));
- }
- }
-#endif
- string msgType = reader.GetString(0);
- SqlClientEventSource.Log.TryNotificationTraceEvent(" msgType: '{0}'", msgType);
- handle = reader.GetGuid(1);
-
- // Only process QueryNotification messages.
- if (0 == String.Compare(msgType, "http://schemas.microsoft.com/SQL/Notifications/QueryNotification", StringComparison.OrdinalIgnoreCase))
- {
- SqlXml payload = reader.GetSqlXml(2);
- if (null != payload)
- {
- SqlNotification notification = SqlNotificationParser.ProcessMessage(payload);
- if (null != notification)
- {
- string key = notification.Key;
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Key: '{0}'", key);
- int index = key.IndexOf(';'); // Our format is simple: "AppDomainKey;commandHash"
-
- if (index >= 0)
- { // Ensure ';' present.
- string appDomainKey = key.Substring(0, index);
- SqlDependencyPerAppDomainDispatcher dispatcher;
- lock (_staticInstance._sqlDependencyPerAppDomainDispatchers)
- {
- dispatcher = _staticInstance._sqlDependencyPerAppDomainDispatchers[appDomainKey];
- }
- if (null != dispatcher)
- {
- try
- {
- dispatcher.InvalidateCommandID(notification); // CROSS APP-DOMAIN CALL!
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure. User event could throw exception.
- }
- }
- else
- {
- Debug.Assert(false, "Received notification but do not have an associated PerAppDomainDispatcher!");
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Received notification but do not have an associated PerAppDomainDispatcher!");
- }
- }
- else
- {
- Debug.Assert(false, "Unexpected ID format received!");
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Unexpected ID format received!");
- }
- }
- else
- {
- Debug.Assert(false, "Null notification returned from ProcessMessage!");
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Null notification returned from ProcessMessage!");
- }
- }
- else
- {
- Debug.Assert(false, "Null payload for QN notification type!");
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Null payload for QN notification type!");
- }
- }
- else
- {
- handle = Guid.Empty;
- // VSDD 546707: this assert was hit by SQL Notification fuzzing tests, disable it to let these tests run on Debug bits
- // Debug.Assert(false, "Unexpected message format received!");
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Unexpected message format received!");
- }
- }
- }
- }
- finally
- {
- // Since we do not want to make a separate round trip just for the end conversation call, we need to
- // batch it with the next command.
- if (handle == Guid.Empty)
- { // This should only happen if failure occurred, or if non-QN format received.
- _com.CommandText = (null != _beginConversationQuery) ? _beginConversationQuery : _receiveQuery; // If we're doing the initial query, we won't have a conversation Guid to begin yet.
- if (_com.Parameters.Count > 1)
- { // Remove conversation param since next execute is only query.
- _com.Parameters.Remove(_conversationGuidParam);
- }
- Debug.Assert(_com.Parameters.Count == 1, "Unexpected number of parameters!");
- }
- else
- {
- _com.CommandText = _concatQuery; // END query + WAITFOR RECEIVE query.
- _conversationGuidParam.Value = handle; // Set value for conversation handle.
- if (_com.Parameters.Count == 1)
- { // Add parameter if previous execute was only query.
- _com.Parameters.Add(_conversationGuidParam);
- }
- Debug.Assert(_com.Parameters.Count == 2, "Unexpected number of parameters!");
- }
- }
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- // SxS: this method uses WindowsIdentity.Impersonate to impersonate the current thread with the
- // credentials used to create this SqlConnectionContainer.
- [ResourceExposure(ResourceScope.None)]
- [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
- private void Restart(object unused)
- {
- // Unused arg required by TimerCallback.
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
- try
- {
- try
- {
- lock (this)
- {
- if (!_stop)
- { // Only execute if we are still in running state.
- try
- {
- _con.Close();
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e); // Discard close failure, if it occurs. Only trace it.
- }
- }
- }
-
- // Rather than one long lock - take lock 3 times for shorter periods.
-
- lock (this)
- {
- if (!_stop)
- {
- if (null != _hashHelper.Identity)
- { // Only impersonate if Integrated Security.
- WindowsImpersonationContext context = null;
- RuntimeHelpers.PrepareConstrainedRegions(); // CER for context.Undo.
- try
- {
- context = _windowsIdentity.Impersonate();
- _con.Open();
- }
- finally
- {
- if (null != context)
- {
- context.Undo();
- }
- }
- }
- else
- { // Else SQL Authentication.
- _con.Open();
- }
- }
- }
-
- lock (this)
- {
- if (!_stop)
- {
- if (_serviceQueueCreated)
- {
- bool failure = false;
-
- try
- {
- CreateQueueAndService(true); // Ensure service, queue, etc is present, if we created it.
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now.
- failure = true;
- }
-
- if (failure)
- {
- // If we failed to re-created queue, service, sproc - invalidate!
- _staticInstance.Invalidate(Server,
- new SqlNotification(SqlNotificationInfo.Error,
- SqlNotificationSource.Client,
- SqlNotificationType.Change,
- null));
-
- }
- }
- }
- }
-
- lock (this)
- {
- if (!_stop)
- {
- _timeoutParam.Value = 0; // Reset timeout to zero - we do not want to block.
- SynchronouslyQueryServiceBrokerQueue();
- // If the above succeeds, we are back in success case - requeue for async call.
- _timeoutParam.Value = _defaultWaitforTimeout; // If success, reset to default for re-queue.
- AsynchronouslyQueryServiceBrokerQueue();
- _errorState = false;
- _retryTimer = null;
- }
- }
-
- if (_stop)
- {
- TearDownAndDispose(); // Function will lock(this).
- }
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e);
-
- try
- {
- // If unexpected query or connection failure, invalidate all dependencies against this server.
- // We may over-notify if only some of the connections to a particular database were affected,
- // but this should not be frequent enough to be a concern.
- // NOTE - we invalidate after failure first occurs and then retry fails. We will then continue
- // to invalidate every time the retry fails.
- _staticInstance.Invalidate(Server,
- new SqlNotification(SqlNotificationInfo.Error,
- SqlNotificationSource.Client,
- SqlNotificationType.Change,
- null));
- }
- catch (Exception f)
- {
- if (!ADP.IsCatchableExceptionType(f))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(f); // Discard exception from Invalidate. User events can throw.
- }
-
- try
- {
- _con.Close();
- }
- catch (Exception f)
- {
- if (!ADP.IsCatchableExceptionType(f))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(f); // Discard close failure, if it occurs. Only trace it.
- }
-
- if (!_stop)
- {
- // Create a timer to callback in one minute, retrying the call to Restart().
- _retryTimer = new Timer(new TimerCallback(Restart), null, _defaultWaitforTimeout, Timeout.Infinite);
- // We will retry this indefinitely, until success - or Stop();
- }
- }
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- internal bool Stop(string appDomainKey, out bool appDomainStop)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
- try
- {
- appDomainStop = false;
-
- // Dictionary used to track how many times start has been called per app domain.
- // For each decrement, subtract from count, and delete if we reach 0.
-
- // TODO BUG UNDONE - once it's decided we don't need AppDomain.UnloadEvent logic below, this should
- // never be null.
- if (null != appDomainKey)
- {
- // If null, then this was called from SqlDependencyProcessDispatcher, we ignore appDomainKeyHash.
- lock (_appDomainKeyHash)
- {
- if (_appDomainKeyHash.ContainsKey(appDomainKey))
- { // Do nothing if AppDomain did not call Start!
- int value = _appDomainKeyHash[appDomainKey];
-
- Debug.Assert(value > 0, "Unexpected count for appDomainKey");
- SqlClientEventSource.Log.TryNotificationTraceEvent(" _appDomainKeyHash contained AppDomainKey: '{0}', pre-decrement Count: '{1}'.", appDomainKey, value);
-
- if (value > 0)
- {
- _appDomainKeyHash[appDomainKey] = value - 1;
- }
- else
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" ERROR pre-decremented count <= 0!");
- Debug.Assert(false, "Unexpected AppDomainKey count in Stop()");
- }
-
- if (1 == value)
- { // Remove from dictionary if pre-decrement count was one.
- _appDomainKeyHash.Remove(appDomainKey);
- appDomainStop = true;
- }
- }
- else
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" ERROR appDomainKey not null and not found in hash!");
- Debug.Assert(false, "Unexpected state on Stop() - no AppDomainKey entry in hashtable!");
- }
- }
- }
-
- Debug.Assert(_startCount > 0, "About to decrement _startCount less than 0!");
- int result = Interlocked.Decrement(ref _startCount);
-
- if (0 == result)
- {
- // If we've reached refCount 0, destroy.
- // Lock to ensure Cancel() complete prior to other thread calling TearDown.
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Reached 0 count, cancelling and waiting.");
-
- lock (this)
- {
- try
- {
- // Race condition with executing thread - will throw if connection is closed due to failure.
- // Rather than fighting the race condition, just call it and discard any potential failure.
- _com.Cancel(); // Cancel the pending command. No-op if connection closed.
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure, if it should occur.
- }
- _stop = true;
- }
-
- // Wait until stopped and service & queue are dropped.
- // TODO: investigate a more appropriate/robust way to ensure this is torn down correctly
- Stopwatch retryStopwatch = Stopwatch.StartNew();
- while (true)
- {
- lock (this)
- {
- if (_stopped)
- {
- break;
- }
-
- // If we are in error state (_errorState is true), force a tear down.
- // Likewise, if we have exceeded the maximum retry period (30 seconds) waiting for cleanup, force a tear down.
- // In rare cases during app domain unload, the async cleanup performed by AsyncResultCallback
- // may fail to execute TearDownAndDispose, leaving this method in an infinite loop, see Dev10#923666.
- // To avoid the infinite loop, we force the cleanup here after 30 seconds. Since we have reached
- // refcount of 0, either this method call or the thread running AsyncResultCallback is responsible for calling
- // TearDownAndDispose when transitioning to the _stopped state. Failing to call TearDownAndDispose means we leak
- // the service broker objects created by this SqlDependency instance, so we make a best effort here to call
- // TearDownAndDispose in the maximum retry period case as well as in the _errorState case.
- if (_errorState || retryStopwatch.Elapsed.Seconds >= 30)
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" forcing cleanup. elapsedSeconds: '{0}', _errorState: '{1}'.", retryStopwatch.Elapsed.Seconds, _errorState);
- Timer retryTimer = _retryTimer;
- _retryTimer = null;
- if (retryTimer != null)
- {
- retryTimer.Dispose(); // Dispose timer - stop retry loop!
- }
- TearDownAndDispose(); // Will not hit server unless connection open!
- break;
- }
- }
-
- // Yield the thread since the stop has not yet completed.
- // VSDD 590625: To avoid CPU spikes while waiting, yield and wait for at least one millisecond
- Thread.Sleep(1);
- }
- }
- else
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" _startCount not 0 after decrement. _startCount: '{0}'.", _startCount);
- }
-
- Debug.Assert(0 <= _startCount, "Invalid start count state");
-
- return _stopped;
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- private void SynchronouslyQueryServiceBrokerQueue()
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
-
- try
- {
- using (SqlDataReader reader = _com.ExecuteReader())
- {
- ProcessNotificationResults(reader);
- }
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- [SuppressMessage("Microsoft.Security", "CA2100:ReviewSqlQueriesForSecurityVulnerabilities")]
- private void TearDownAndDispose()
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
-
- try
- {
- lock (this)
- { // Lock to ensure Stop() (with Cancel()) complete prior to TearDown.
- try
- {
- // Only execute if connection is still up and open.
- if (ConnectionState.Closed != _con.State && ConnectionState.Broken != _con.State)
- {
- if (_com.Parameters.Count > 1)
- { // Need to close dialog before completing.
- // In the normal case, the "End Conversation" query is executed before a
- // receive query and upon return we will clear the state. However, unless
- // a non notification query result is returned, we will not clear it. That
- // means a query is generally always executing with an "end conversation" on
- // the wire. Rather than synchronize for success of the other "end conversation",
- // simply re-execute.
- try
- {
- _com.CommandText = _endConversationQuery;
- _com.Parameters.Remove(_timeoutParam);
- _com.ExecuteNonQuery();
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure.
- }
- }
-
- if (_serviceQueueCreated && !_errorState)
- {
- /*
- BEGIN TRANSACTION;
- DROP SERVICE "+_escapedQueueName+";
- DROP QUEUE "+_escapedQueueName+";
- DROP PROCEDURE "+_sprocName+";
- COMMIT TRANSACTION;
- */
- _com.CommandText = "BEGIN TRANSACTION; DROP SERVICE " + _escapedQueueName + "; DROP QUEUE " + _escapedQueueName + "; DROP PROCEDURE " + _sprocName + "; COMMIT TRANSACTION;";
- try
- {
- _com.ExecuteNonQuery();
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure.
- }
- }
- }
- }
- finally
- {
- _stopped = true;
- _con.Dispose(); // Close and dispose connection.
- //dispose windows identity
- if (_windowsIdentity != null)
- {
- _windowsIdentity.Dispose();
- }
- }
- }
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
- }
- // -----------------------------------------
- // END SqlConnectionContainer private class.
- // -----------------------------------------
-
-
-
- // -------------------------------------------------------------------
- // Private class encapsulating the notification payload parsing logic.
- // -------------------------------------------------------------------
-
- // TODO BUG UNDONE - NEED TO REVIEW AND POSSIBLY CORRECT SOME OF BELOW...
-
- private class SqlNotificationParser
- {
- [Flags]
- private enum MessageAttributes
- {
- None = 0,
- Type = 1,
- Source = 2,
- Info = 4,
- All = Type + Source + Info,
- }
-
- // node names in the payload
- private const string RootNode = "QueryNotification";
- private const string MessageNode = "Message";
-
- // attribute names (on the QueryNotification element)
- private const string InfoAttribute = "info";
- private const string SourceAttribute = "source";
- private const string TypeAttribute = "type";
-
- internal static SqlNotification ProcessMessage(SqlXml xmlMessage)
- {
- using (XmlReader xmlReader = xmlMessage.CreateReader())
- {
- string keyvalue = String.Empty;
-
- MessageAttributes messageAttributes = MessageAttributes.None;
-
- SqlNotificationType type = SqlNotificationType.Unknown;
- SqlNotificationInfo info = SqlNotificationInfo.Unknown;
- SqlNotificationSource source = SqlNotificationSource.Unknown;
-
- string key = string.Empty;
-
- // Move to main node, expecting "QueryNotification".
- xmlReader.Read();
- if ((XmlNodeType.Element == xmlReader.NodeType) &&
- (RootNode == xmlReader.LocalName) &&
- (3 <= xmlReader.AttributeCount))
- {
- // Loop until we've processed all the attributes.
- while ((MessageAttributes.All != messageAttributes) && (xmlReader.MoveToNextAttribute()))
- {
- try
- {
- switch (xmlReader.LocalName)
- {
- case TypeAttribute:
- try
- {
- SqlNotificationType temp = (SqlNotificationType)Enum.Parse(typeof(SqlNotificationType), xmlReader.Value, true);
- if (Enum.IsDefined(typeof(SqlNotificationType), temp))
- {
- type = temp;
- }
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure, if it should occur.
- }
- messageAttributes |= MessageAttributes.Type;
- break;
- case SourceAttribute:
- try
- {
- SqlNotificationSource temp = (SqlNotificationSource)Enum.Parse(typeof(SqlNotificationSource), xmlReader.Value, true);
- if (Enum.IsDefined(typeof(SqlNotificationSource), temp))
- {
- source = temp;
- }
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure, if it should occur.
- }
- messageAttributes |= MessageAttributes.Source;
- break;
- case InfoAttribute:
- try
- {
- string value = xmlReader.Value;
- // SQL BU DT 390529 - 3 of the server info values do not match client values - map.
- switch (value)
- {
- case "set options":
- info = SqlNotificationInfo.Options;
- break;
- case "previous invalid":
- info = SqlNotificationInfo.PreviousFire;
- break;
- case "query template limit":
- info = SqlNotificationInfo.TemplateLimit;
- break;
- default:
- SqlNotificationInfo temp = (SqlNotificationInfo)Enum.Parse(typeof(SqlNotificationInfo), value, true);
- if (Enum.IsDefined(typeof(SqlNotificationInfo), temp))
- {
- info = temp;
- }
- break;
- }
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure, if it should occur.
- }
- messageAttributes |= MessageAttributes.Info;
- break;
- default:
- break;
- }
- }
- catch (ArgumentException e)
- {
- ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace.
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Exception thrown - Enum.Parse failed to parse the value '{0}' of the attribute '{1}'.", xmlReader.Value, xmlReader.LocalName);
- return null;
- }
- }
-
- if (MessageAttributes.All != messageAttributes)
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Not all expected attributes in Message; messageAttributes = '{0}'.", (int)messageAttributes);
- return null;
- }
-
- // Proceed to the "Message" node.
- if (!xmlReader.Read())
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
- return null;
- }
-
- // Verify state after Read().
- if ((XmlNodeType.Element != xmlReader.NodeType) || (0 != string.Compare(xmlReader.LocalName, MessageNode, StringComparison.OrdinalIgnoreCase)))
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
- return null;
- }
-
- // Proceed to the Text Node.
- if (!xmlReader.Read())
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
- return null;
- }
-
- // Verify state after Read().
- if (xmlReader.NodeType != XmlNodeType.Text)
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
- return null;
- }
-
- // Create a new XmlTextReader on the Message node value. Prohibit DTD processing when dealing with untrusted sources.
- using (XmlTextReader xmlMessageReader = new XmlTextReader(xmlReader.Value, XmlNodeType.Element, null) { DtdProcessing = DtdProcessing.Prohibit })
- {
- // Proceed to the Text Node.
- if (!xmlMessageReader.Read())
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
- return null;
- }
-
- if (xmlMessageReader.NodeType == XmlNodeType.Text)
- {
- key = xmlMessageReader.Value;
- xmlMessageReader.Close();
- }
- else
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
- return null;
- }
- }
-
- return new SqlNotification(info, source, type, key);
- }
- else
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
- return null; // failure
- }
- }
- }
- }
- // ----------------------------------------
- // END SqlNotificationParser private class.
- // ----------------------------------------
-
-
-
- // ------------------------------------------------------------------
- // Private class encapsulating the SqlConnectionContainer hash logic.
- // ------------------------------------------------------------------
-
- private class SqlConnectionContainerHashHelper
- {
- // For default, queue is computed in SqlConnectionContainer constructor, so queue will be empty and
- // connection string will not include app name based on queue. As a result, the connection string
- // builder will always contain up to date info, but _connectionString and _queue will not.
-
- // As a result, we will not use _connectionStringBuilder as part of Equals or GetHashCode.
-
- private DbConnectionPoolIdentity _identity;
- private string _connectionString;
- private string _queue;
- private SqlConnectionStringBuilder _connectionStringBuilder; // Not to be used for comparison!
-
- internal SqlConnectionContainerHashHelper(DbConnectionPoolIdentity identity, string connectionString,
- string queue, SqlConnectionStringBuilder connectionStringBuilder)
- {
- _identity = identity;
- _connectionString = connectionString;
- _queue = queue;
- _connectionStringBuilder = connectionStringBuilder;
- }
- /*
- internal string ConnectionString {
- get {
- return _connectionString;
- }
- }
- */
- internal SqlConnectionStringBuilder ConnectionStringBuilder
- { // Not to be used for comparison!
- get
- {
- return _connectionStringBuilder;
- }
- }
-
- internal DbConnectionPoolIdentity Identity
- {
- get
- {
- return _identity;
- }
- }
-
- internal string Queue
- {
- get
- {
- return _queue;
- }
- }
-
- override public bool Equals(object value)
- {
- SqlConnectionContainerHashHelper temp = (SqlConnectionContainerHashHelper)value;
-
- bool result = false;
-
- // Ignore SqlConnectionStringBuilder, since it is present largely for debug purposes.
-
- if (null == temp)
- { // If passed value null - false.
- result = false;
- }
- else if (this == temp)
- { // If instances equal - true.
- result = true;
- }
- else
- {
- if ((_identity != null && temp._identity == null) || // If XOR of null identities false - false.
- (_identity == null && temp._identity != null))
- {
- result = false;
- }
- else if (_identity == null && temp._identity == null)
- {
- if (temp._connectionString == _connectionString &&
- String.Equals(temp._queue, _queue, StringComparison.OrdinalIgnoreCase))
- {
- result = true;
- }
- else
- {
- result = false;
- }
- }
- else
- {
- if (temp._identity.Equals(_identity) &&
- temp._connectionString == _connectionString &&
- String.Equals(temp._queue, _queue, StringComparison.OrdinalIgnoreCase))
- {
- result = true;
- }
- else
- {
- result = false;
- }
- }
- }
-
- return result;
- }
-
- override public int GetHashCode()
- {
- int hashValue = 0;
-
- if (null != _identity)
- {
- hashValue = _identity.GetHashCode();
- }
-
- if (null != _queue)
- {
- hashValue = unchecked(_connectionString.GetHashCode() + _queue.GetHashCode() + hashValue);
- }
- else
- {
- hashValue = unchecked(_connectionString.GetHashCode() + hashValue);
- }
-
- return hashValue;
- }
- }
- // ----------------------------------------
- // END SqlConnectionContainerHashHelper private class.
- // ----------------------------------------
-
-
-
- // ---------------------------------------------
- // SqlDependencyProcessDispatcher static members
- // ---------------------------------------------
-
- private static SqlDependencyProcessDispatcher _staticInstance = new SqlDependencyProcessDispatcher(null);
-
- // Dictionaries used as maps.
- private Dictionary _connectionContainers; // NT_ID+ConStr+Service->Container
- private Dictionary _sqlDependencyPerAppDomainDispatchers; // AppDomainKey->Callback
-
- // -----------
- // BID members
- // -----------
-
- private readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
- private static int _objectTypeCount; // EventSource Counter
- internal int ObjectID
- {
- get
- {
- return _objectID;
- }
- }
-
- // ------------
- // Constructors
- // ------------
-
- // Private constructor - only called by public constructor for static initialization.
- private SqlDependencyProcessDispatcher(object dummyVariable)
- {
- Debug.Assert(null == _staticInstance, "Real constructor called with static instance already created!");
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
- try
- {
-#if DEBUG
- // Possibly expensive, limit to debug.
- SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, AppDomain.CurrentDomain.FriendlyName: {1}", ObjectID, AppDomain.CurrentDomain.FriendlyName);
-#endif
- _connectionContainers = new Dictionary();
- _sqlDependencyPerAppDomainDispatchers = new Dictionary();
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- // Constructor is only called by remoting.
- // Required to be public, even on internal class, for Remoting infrastructure.
- public SqlDependencyProcessDispatcher()
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
- try
- {
- // Empty constructor and object - dummy to obtain singleton.
-#if DEBUG
- // Possibly expensive, limit to debug.
- SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, AppDomain.CurrentDomain.FriendlyName: {1}", ObjectID, AppDomain.CurrentDomain.FriendlyName);
-#endif
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- // ----------
- // Properties
- // ----------
-
- internal SqlDependencyProcessDispatcher SingletonProcessDispatcher
- {
- get
- {
- return _staticInstance;
- }
- }
-
- // -----------------------
- // Various private methods
- // -----------------------
-
- private static SqlConnectionContainerHashHelper GetHashHelper(string connectionString,
- out SqlConnectionStringBuilder connectionStringBuilder,
- out DbConnectionPoolIdentity identity,
- out string user,
- string queue)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, queue: {1}", _staticInstance.ObjectID, queue);
- try
- {
- // Force certain connection string properties to be used by SqlDependencyProcessDispatcher.
- // This logic is done here to enable us to have the complete connection string now to be used
- // for tracing as we flow through the logic.
- connectionStringBuilder = new SqlConnectionStringBuilder(connectionString);
- connectionStringBuilder.Pooling = false;
- connectionStringBuilder.Enlist = false;
- connectionStringBuilder.ConnectRetryCount = 0;
- if (null != queue)
- { // User provided!
- connectionStringBuilder.ApplicationName = queue; // ApplicationName will be set to queue name.
- }
-
- if (connectionStringBuilder.IntegratedSecurity)
- {
- // Use existing identity infrastructure for error cases and proper hash value.
- identity = DbConnectionPoolIdentity.GetCurrent();
- user = null;
- }
- else
- {
- identity = null;
- user = connectionStringBuilder.UserID;
- }
-
- return new SqlConnectionContainerHashHelper(identity, connectionStringBuilder.ConnectionString,
- queue, connectionStringBuilder);
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- // Needed for remoting to prevent lifetime issues and default GC cleanup.
- public override object InitializeLifetimeService()
- {
- return null;
- }
-
- private void Invalidate(string server, SqlNotification sqlNotification)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, server: {1}", ObjectID, server);
- try
- {
- Debug.Assert(this == _staticInstance, "Instance method called on non _staticInstance instance!");
- lock (_sqlDependencyPerAppDomainDispatchers)
- {
-
- foreach (KeyValuePair entry in _sqlDependencyPerAppDomainDispatchers)
- {
- SqlDependencyPerAppDomainDispatcher perAppDomainDispatcher = entry.Value;
- try
- {
- perAppDomainDispatcher.InvalidateServer(server, sqlNotification);
- }
- catch (Exception f)
- {
- // Since we are looping over dependency dispatchers, do not allow one Invalidate
- // that results in a throw prevent us from invalidating all dependencies
- // related to this server.
- // NOTE - SqlDependencyPerAppDomainDispatcher already wraps individual dependency invalidates
- // with try/catch, but we should be careful and do the same here.
- if (!ADP.IsCatchableExceptionType(f))
- {
- throw;
- }
- ADP.TraceExceptionWithoutRethrow(f); // Discard failure, but trace.
- }
- }
- }
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- // ----------------------------------------------------
- // Clean-up method initiated by other AppDomain.Unloads
- // ----------------------------------------------------
-
- // Individual AppDomains upon AppDomain.UnloadEvent will call this method.
- internal void QueueAppDomainUnloading(string appDomainKey)
- {
- ThreadPool.QueueUserWorkItem(new WaitCallback(AppDomainUnloading), appDomainKey);
- }
-
- // This method is only called by queued work-items from the method above.
- private void AppDomainUnloading(object state)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
- try
- {
- string appDomainKey = (string)state;
-
- Debug.Assert(this == _staticInstance, "Instance method called on non _staticInstance instance!");
- lock (_connectionContainers)
- {
- List containersToRemove = new List();
-
- foreach (KeyValuePair entry in _connectionContainers)
- {
- SqlConnectionContainer container = entry.Value;
- if (container.AppDomainUnload(appDomainKey))
- { // Perhaps wrap in try catch.
- containersToRemove.Add(container.HashHelper);
- }
- }
-
- foreach (SqlConnectionContainerHashHelper hashHelper in containersToRemove)
- {
- _connectionContainers.Remove(hashHelper);
- }
- }
-
- lock (_sqlDependencyPerAppDomainDispatchers)
- { // Remove from global Dictionary.
- _sqlDependencyPerAppDomainDispatchers.Remove(appDomainKey);
- }
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- // -------------
- // Start methods
- // -------------
-
- internal bool StartWithDefault(string connectionString,
- out string server,
- out DbConnectionPoolIdentity identity,
- out string user,
- out string database,
- ref string service,
- string appDomainKey,
- SqlDependencyPerAppDomainDispatcher dispatcher,
- out bool errorOccurred,
- out bool appDomainStart)
- {
- Debug.Assert(this == _staticInstance, "Instance method called on non _staticInstance instance!");
- return Start(connectionString,
- out server,
- out identity,
- out user,
- out database,
- ref service,
- appDomainKey,
- dispatcher,
- out errorOccurred,
- out appDomainStart,
- true);
- }
-
- internal bool Start(string connectionString,
- string queue,
- string appDomainKey,
- SqlDependencyPerAppDomainDispatcher dispatcher)
- {
- Debug.Assert(this == _staticInstance, "Instance method called on non _staticInstance instance!");
- string dummyValue1 = null;
- bool dummyValue2 = false;
- DbConnectionPoolIdentity dummyValue3 = null;
- return Start(connectionString,
- out dummyValue1,
- out dummyValue3,
- out dummyValue1,
- out dummyValue1,
- ref queue,
- appDomainKey,
- dispatcher,
- out dummyValue2,
- out dummyValue2,
- false);
- }
-
- private bool Start(string connectionString,
- out string server,
- out DbConnectionPoolIdentity identity,
- out string user,
- out string database,
- ref string queueService,
- string appDomainKey,
- SqlDependencyPerAppDomainDispatcher dispatcher,
- out bool errorOccurred,
- out bool appDomainStart,
- bool useDefaults)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, queue: '{1}', appDomainKey: '{2}', perAppDomainDispatcher ID: '{3}'", ObjectID, queueService, appDomainKey, dispatcher.ObjectID);
- try
- {
- Debug.Assert(this == _staticInstance, "Instance method called on non _staticInstance instance!");
- server = null; // Reset out params.
- identity = null;
- user = null;
- database = null;
- errorOccurred = false;
- appDomainStart = false;
-
- lock (_sqlDependencyPerAppDomainDispatchers)
- {
- if (!_sqlDependencyPerAppDomainDispatchers.ContainsKey(appDomainKey))
- {
- _sqlDependencyPerAppDomainDispatchers[appDomainKey] = dispatcher;
- }
- }
-
- SqlConnectionStringBuilder connectionStringBuilder = null;
- SqlConnectionContainerHashHelper hashHelper = GetHashHelper(connectionString,
- out connectionStringBuilder,
- out identity,
- out user,
- queueService);
-#if DEBUG
- SqlConnectionString connectionStringOptions = new SqlConnectionString(connectionStringBuilder.ConnectionString);
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Modified connection string: '{0}'", connectionStringOptions.UsersConnectionStringForTrace());
-#endif
-
- bool started = false;
-
- SqlConnectionContainer container = null;
- lock (_connectionContainers)
- {
- if (!_connectionContainers.ContainsKey(hashHelper))
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, hashtable miss, creating new container.", ObjectID);
- container = new SqlConnectionContainer(hashHelper, appDomainKey, useDefaults);
- _connectionContainers.Add(hashHelper, container);
- started = true;
- appDomainStart = true;
- }
- else
- {
- container = _connectionContainers[hashHelper];
- SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, hashtable hit, container: {1}", ObjectID, container.ObjectID);
- if (container.InErrorState)
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, container: {1} is in error state!", ObjectID, container.ObjectID);
- errorOccurred = true; // Set outparam errorOccurred true so we invalidate on Start().
- }
- else
- {
- container.IncrementStartCount(appDomainKey, out appDomainStart);
- }
- }
- }
-
- if (useDefaults && !errorOccurred)
- { // Return server, database, and queue for use by SqlDependency.
- server = container.Server;
- database = container.Database;
- queueService = container.Queue;
- SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, default service: '{1}', server: '{2}', database: '{3}'", ObjectID, queueService, server, database);
- }
-
- SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, started: {1}", ObjectID, started);
- return started;
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- // ------------
- // Stop methods
- // ------------
-
- internal bool Stop(string connectionString,
- out string server,
- out DbConnectionPoolIdentity identity,
- out string user,
- out string database,
- ref string queueService,
- string appDomainKey,
- out bool appDomainStop)
- {
- long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, queue: '{1}'", ObjectID, queueService);
- try
- {
- Debug.Assert(this == _staticInstance, "Instance method called on non _staticInstance instance!");
- server = null; // Reset out param.
- identity = null;
- user = null;
- database = null;
- appDomainStop = false;
-
- SqlConnectionStringBuilder connectionStringBuilder = null;
- SqlConnectionContainerHashHelper hashHelper = GetHashHelper(connectionString,
- out connectionStringBuilder,
- out identity,
- out user,
- queueService);
-#if DEBUG
- SqlConnectionString connectionStringOptions = new SqlConnectionString(connectionStringBuilder.ConnectionString);
- SqlClientEventSource.Log.TryNotificationTraceEvent(" Modified connection string: '{0}'", connectionStringOptions.UsersConnectionStringForTrace());
-#endif
-
- bool stopped = false;
-
- lock (_connectionContainers)
- {
- if (_connectionContainers.ContainsKey(hashHelper))
- {
- SqlConnectionContainer container = _connectionContainers[hashHelper];
- SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, hashtable hit, container: {1}", ObjectID, container.ObjectID);
-
- server = container.Server; // Return server, database, and queue info for use by calling SqlDependency.
- database = container.Database;
- queueService = container.Queue;
- if (container.Stop(appDomainKey, out appDomainStop))
- { // Stop can be blocking if refCount == 0 on container.
- stopped = true;
- _connectionContainers.Remove(hashHelper); // Remove from collection.
- }
- }
- else
- {
- SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, hashtable miss.", ObjectID);
- }
- }
-
- SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, stopped: {1}", ObjectID, stopped);
- return stopped;
- }
- finally
- {
- SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
- }
- }
-
- // -----------------------------------------
- // END SqlDependencyProcessDispatcher class.
- // -----------------------------------------
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs
index 80692b242d..6a14a5ef8e 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs
@@ -484,7 +484,7 @@ private static void ObtainProcessDispatcher()
if (null != dependency)
{
- s_processDispatcher = dependency.SingletonProcessDispatcher; // Set to static instance.
+ s_processDispatcher = SqlDependencyProcessDispatcher.SingletonProcessDispatcher; // Set to static instance.
// Serialize and set in native.
using (MemoryStream stream = new())
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs
similarity index 92%
rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs
index 6bf21e5a59..5867bc3327 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs
@@ -8,6 +8,12 @@
using System.Data.SqlTypes;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+#if NETFRAMEWORK
+using System.Runtime.CompilerServices;
+using System.Runtime.Versioning;
+using System.Security.Principal;
+using Microsoft.Data;
+#endif
using System.Threading;
using System.Xml;
using Microsoft.Data.Common;
@@ -23,21 +29,24 @@ internal class SqlDependencyProcessDispatcher : MarshalByRefObject
// Class to contain/store all relevant information about a connection that waits on the SSB queue.
private class SqlConnectionContainer
{
- private SqlConnection _con;
- private SqlCommand _com;
- private SqlParameter _conversationGuidParam;
- private SqlParameter _timeoutParam;
- private SqlConnectionContainerHashHelper _hashHelper;
- private string _queue;
- private string _receiveQuery;
+ private readonly SqlConnection _con;
+ private readonly SqlCommand _com;
+ private readonly SqlParameter _conversationGuidParam;
+ private readonly SqlParameter _timeoutParam;
+ private readonly SqlConnectionContainerHashHelper _hashHelper;
+#if NETFRAMEWORK
+ private readonly WindowsIdentity _windowsIdentity;
+#endif
+ private readonly string _queue;
+ private readonly string _receiveQuery;
private string _beginConversationQuery;
private string _endConversationQuery;
private string _concatQuery;
private readonly int _defaultWaitforTimeout = 60000; // Waitfor(Receive) timeout (milleseconds)
- private string _escapedQueueName;
- private string _sprocName;
+ private readonly string _escapedQueueName;
+ private readonly string _sprocName;
private string _dialogHandle;
- private string _cachedServer;
+ private readonly string _cachedServer;
private string _cachedDatabase;
private volatile bool _errorState = false;
private volatile bool _stop = false; // Can probably simplify this slightly - one bool instead of two.
@@ -46,10 +55,10 @@ private class SqlConnectionContainer
private int _startCount = 0; // Each container class is called once per Start() - we refCount
// to track when we can dispose.
private Timer _retryTimer = null;
- private Dictionary _appDomainKeyHash = null; // AppDomainKey->Open RefCount
+ private readonly Dictionary _appDomainKeyHash = null; // AppDomainKey->Open RefCount
- private static int _objectTypeCount; // EventSource counter
- internal int ObjectID { get; } = Interlocked.Increment(ref _objectTypeCount);
+ private static int s_objectTypeCount; // EventSource counter
+ internal int ObjectID { get; } = Interlocked.Increment(ref s_objectTypeCount);
// Constructor
@@ -75,7 +84,7 @@ internal SqlConnectionContainer(SqlConnectionContainerHashHelper hashHelper, str
_queue = _hashHelper.Queue;
}
#if DEBUG
- SqlConnectionString connectionStringOptions = new SqlConnectionString(_hashHelper.ConnectionStringBuilder.ConnectionString);
+ SqlConnectionString connectionStringOptions = new(_hashHelper.ConnectionStringBuilder.ConnectionString);
SqlClientEventSource.Log.TryNotificationTraceEvent(" Modified connection string: '{0}'", connectionStringOptions.UsersConnectionStringForTrace());
#endif
@@ -86,11 +95,27 @@ internal SqlConnectionContainer(SqlConnectionContainerHashHelper hashHelper, str
// Assert permission for this particular connection string since it differs from the user passed string
// which we have already demanded upon.
SqlConnectionString connStringObj = (SqlConnectionString)_con.ConnectionOptions;
-
+#if NETFRAMEWORK
+ connStringObj.CreatePermissionSet().Assert();
+ if (connStringObj.LocalDBInstance != null)
+ {
+ // If it is LocalDB, we demanded LocalDB permissions too
+ LocalDBAPI.AssertLocalDBPermissions();
+ }
+#endif
_con.Open();
_cachedServer = _con.DataSource;
+#if NETFRAMEWORK
+ if (hashHelper.Identity != null)
+ {
+ // For now, DbConnectionPoolIdentity does not cache WindowsIdentity.
+ // That means for every container creation, we create a WindowsIdentity twice.
+ // We may want to improve this.
+ _windowsIdentity = DbConnectionPoolIdentity.GetCurrentWindowsIdentity();
+ }
+#endif
_escapedQueueName = SqlConnection.FixupDatabaseTransactionName(_queue); // Properly escape to prevent SQL Injection.
_appDomainKeyHash = new Dictionary(); // Dictionary stores the Start/Stop refcount per AppDomain for this container.
_com = new SqlCommand()
@@ -260,7 +285,7 @@ private void AsynchronouslyQueryServiceBrokerQueue()
long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
try
{
- AsyncCallback callback = new AsyncCallback(AsyncResultCallback);
+ AsyncCallback callback = new(AsyncResultCallback);
_com.BeginExecuteReader(callback, null, CommandBehavior.Default); // NO LOCK NEEDED
}
finally
@@ -326,7 +351,7 @@ private void CreateQueueAndService(bool restart)
long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
try
{
- SqlCommand com = new SqlCommand()
+ SqlCommand com = new()
{
Connection = _con
};
@@ -414,7 +439,7 @@ private void CreateQueueAndService(bool restart)
+ " END;"
+ " BEGIN DIALOG @dialog_handle FROM SERVICE " + _escapedQueueName + " TO SERVICE " + nameLiteral;
- SqlParameter param = new SqlParameter()
+ SqlParameter param = new()
{
ParameterName = "@dialog_handle",
DbType = DbType.Guid,
@@ -612,6 +637,12 @@ private void ProcessNotificationResults(SqlDataReader reader)
}
}
+#if NETFRAMEWORK
+ // SxS: this method uses WindowsIdentity.Impersonate to impersonate the current thread with the
+ // credentials used to create this SqlConnectionContainer.
+ [ResourceExposure(ResourceScope.None)]
+ [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
+#endif
private void Restart(object unused)
{
long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID);
@@ -645,7 +676,28 @@ private void Restart(object unused)
{
if (!_stop)
{
+#if NETFRAMEWORK
+ if (null != _hashHelper.Identity)
+ { // Only impersonate if Integrated Security.
+ WindowsImpersonationContext context = null;
+ RuntimeHelpers.PrepareConstrainedRegions(); // CER for context.Undo.
+ try
+ {
+ context = _windowsIdentity.Impersonate();
+#endif
_con.Open();
+#if NETFRAMEWORK
+ }
+ finally
+ {
+ context?.Undo();
+ }
+ }
+ else
+ { // Else SQL Authentication.
+ _con.Open();
+ }
+#endif
}
}
@@ -976,6 +1028,10 @@ private void TearDownAndDispose()
{
_stopped = true;
_con.Dispose(); // Close and dispose connection.
+#if NETFRAMEWORK
+ //dispose windows identity
+ _windowsIdentity?.Dispose();
+#endif
}
}
}
@@ -1116,52 +1172,52 @@ internal static SqlNotification ProcessMessage(SqlXml xmlMessage)
catch (ArgumentException e)
{
ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace.
- SqlClientEventSource.Log.TryTraceEvent(" Exception thrown - Enum.Parse failed to parse the value '{0}' of the attribute '{1}'.", xmlReader.Value, xmlReader.LocalName);
+ SqlClientEventSource.Log.TryNotificationTraceEvent(" Exception thrown - Enum.Parse failed to parse the value '{0}' of the attribute '{1}'.", xmlReader.Value, xmlReader.LocalName);
return null;
}
}
if (MessageAttributes.All != messageAttributes)
{
- SqlClientEventSource.Log.TryTraceEvent(" Not all expected attributes in Message; messageAttributes = '{0}'.", (int)messageAttributes);
+ SqlClientEventSource.Log.TryNotificationTraceEvent(" Not all expected attributes in Message; messageAttributes = '{0}'.", (int)messageAttributes);
return null;
}
// Proceed to the "Message" node.
if (!xmlReader.Read())
{
- SqlClientEventSource.Log.TryTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
+ SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
return null;
}
// Verify state after Read().
if ((XmlNodeType.Element != xmlReader.NodeType) || (0 != string.Compare(xmlReader.LocalName, MessageNode, StringComparison.OrdinalIgnoreCase)))
{
- SqlClientEventSource.Log.TryTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
+ SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
return null;
}
// Proceed to the Text Node.
if (!xmlReader.Read())
{
- SqlClientEventSource.Log.TryTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
+ SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
return null;
}
// Verify state after Read().
if (xmlReader.NodeType != XmlNodeType.Text)
{
- SqlClientEventSource.Log.TryTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
+ SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
return null;
}
// Create a new XmlTextReader on the Message node value. Prohibit DTD processing when dealing with untrusted sources.
- using (XmlTextReader xmlMessageReader = new XmlTextReader(xmlReader.Value, XmlNodeType.Element, null) { DtdProcessing = DtdProcessing.Prohibit })
+ using (XmlTextReader xmlMessageReader = new(xmlReader.Value, XmlNodeType.Element, null) { DtdProcessing = DtdProcessing.Prohibit })
{
// Proceed to the Text Node.
if (!xmlMessageReader.Read())
{
- SqlClientEventSource.Log.TryTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
+ SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
return null;
}
@@ -1172,7 +1228,7 @@ internal static SqlNotification ProcessMessage(SqlXml xmlMessage)
}
else
{
- SqlClientEventSource.Log.TryTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
+ SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
return null;
}
}
@@ -1181,7 +1237,7 @@ internal static SqlNotification ProcessMessage(SqlXml xmlMessage)
}
else
{
- SqlClientEventSource.Log.TryTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
+ SqlClientEventSource.Log.TryNotificationTraceEvent(" unexpected Read failure on xml or unexpected structure of xml.");
return null; // failure
}
}
@@ -1198,10 +1254,10 @@ private class SqlConnectionContainerHashHelper
// As a result, we will not use _connectionStringBuilder as part of Equals or GetHashCode.
- private DbConnectionPoolIdentity _identity;
- private string _connectionString;
- private string _queue;
- private SqlConnectionStringBuilder _connectionStringBuilder; // Not to be used for comparison!
+ private readonly DbConnectionPoolIdentity _identity;
+ private readonly string _connectionString;
+ private readonly string _queue;
+ private readonly SqlConnectionStringBuilder _connectionStringBuilder; // Not to be used for comparison!
internal SqlConnectionContainerHashHelper(DbConnectionPoolIdentity identity, string connectionString,
string queue, SqlConnectionStringBuilder connectionStringBuilder)
@@ -1223,7 +1279,7 @@ public override bool Equals(object value)
{
SqlConnectionContainerHashHelper temp = (SqlConnectionContainerHashHelper)value;
- bool result = false;
+ bool result;
// Ignore SqlConnectionStringBuilder, since it is present largely for debug purposes.
@@ -1296,14 +1352,14 @@ public override int GetHashCode()
// SqlDependencyProcessDispatcher static members
- private static SqlDependencyProcessDispatcher s_staticInstance = new SqlDependencyProcessDispatcher(null);
+ private static readonly SqlDependencyProcessDispatcher s_staticInstance = new(null);
// Dictionaries used as maps.
- private Dictionary _connectionContainers; // NT_ID+ConStr+Service->Container
- private Dictionary _sqlDependencyPerAppDomainDispatchers; // AppDomainKey->Callback
+ private readonly Dictionary _connectionContainers; // NT_ID+ConStr+Service->Container
+ private readonly Dictionary _sqlDependencyPerAppDomainDispatchers; // AppDomainKey->Callback
- private static int _objectTypeCount; //EventSource counter
- internal int ObjectID { get; } = Interlocked.Increment(ref _objectTypeCount);
+ private static int s_objectTypeCount; //EventSource counter
+ internal int ObjectID { get; } = Interlocked.Increment(ref s_objectTypeCount);
// Constructors
// Private constructor - only called by public constructor for static initialization.
@@ -1459,7 +1515,7 @@ private void AppDomainUnloading(object state)
Debug.Assert(this == s_staticInstance, "Instance method called on non _staticInstance instance!");
lock (_connectionContainers)
{
- List containersToRemove = new List();
+ List containersToRemove = new();
foreach (KeyValuePair entry in _connectionContainers)
{
@@ -1527,15 +1583,15 @@ internal bool Start(
Debug.Assert(this == s_staticInstance, "Instance method called on non _staticInstance instance!");
return Start(
connectionString,
- out string dummyValue1,
- out DbConnectionPoolIdentity dummyValue3,
- out dummyValue1,
- out dummyValue1,
+ out _,
+ out _,
+ out _,
+ out _,
ref queue,
appDomainKey,
dispatcher,
- out bool dummyValue2,
- out dummyValue2,
+ out _,
+ out _,
false);
}
@@ -1577,7 +1633,7 @@ private bool Start(
out user,
queueService);
#if DEBUG
- SqlConnectionString connectionStringOptions = new SqlConnectionString(connectionStringBuilder.ConnectionString);
+ SqlConnectionString connectionStringOptions = new(connectionStringBuilder.ConnectionString);
SqlClientEventSource.Log.TryNotificationTraceEvent(" Modified connection string: '{0}'", connectionStringOptions.UsersConnectionStringForTrace());
#endif
@@ -1654,7 +1710,7 @@ internal bool Stop(
out user,
queueService);
#if DEBUG
- SqlConnectionString connectionStringOptions = new SqlConnectionString(connectionStringBuilder.ConnectionString);
+ SqlConnectionString connectionStringOptions = new(connectionStringBuilder.ConnectionString);
SqlClientEventSource.Log.TryNotificationTraceEvent(" Modified connection string: '{0}'", connectionStringOptions.UsersConnectionStringForTrace());
#endif