3326 Kusto HTTPS url fix (#1086)

* 3326 Removed sqlConnection.Open from CreateSqlConnection in ConnectionService.

* 3326 Removed unused variables and functions from ConnectedBindingContext, ConnectedBindingQueue, IBindingContext, and IConnectedBindingQueue

* 3326 Removed unused constant SqlAzureEdition and unused functions CreateSqlConnection and CreateServerConnection from ConnectionService. Removed ServerConnection, MetadataDisplayInfoProvider, Binder, and SmoMetaDataProvider from ConnectedBindingContext. Deleted SqlConnectionOpener and ISqlConnectionOpener.
This commit is contained in:
Justin M
2020-10-05 00:56:40 -07:00
committed by GitHub
parent 164ca951da
commit 2b022d2e48
10 changed files with 15 additions and 461 deletions

View File

@@ -31,9 +31,8 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// </summary>
public class ConnectionService
{
public const string AdminConnectionPrefix = "ADMIN:";
internal const string PasswordPlaceholder = "******";
private const string SqlAzureEdition = "SQL Azure";
private const string AdminConnectionPrefix = "ADMIN:";
private const string PasswordPlaceholder = "******";
/// <summary>
/// Singleton service instance
@@ -984,7 +983,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// Build a connection string builder a connection details instance
/// </summary>
/// <param name="connectionDetails"></param>
public static SqlConnectionStringBuilder CreateConnectionStringBuilder(ConnectionDetails connectionDetails)
private static SqlConnectionStringBuilder CreateConnectionStringBuilder(ConnectionDetails connectionDetails)
{
SqlConnectionStringBuilder connectionBuilder;
@@ -1359,63 +1358,6 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
}
}
/// <summary>
/// Create and open a new SqlConnection from a ConnectionInfo object
/// Note: we need to audit all uses of this method to determine why we're
/// bypassing normal ConnectionService connection management
/// </summary>
/// <param name="connInfo">The connection info to connect with</param>
/// <param name="featureName">A plaintext string that will be included in the application name for the connection</param>
/// <returns>A SqlConnection created with the given connection info</returns>
internal static SqlConnection OpenSqlConnection(ConnectionInfo connInfo, string featureName = null)
{
try
{
// capture original values
int? originalTimeout = connInfo.ConnectionDetails.ConnectTimeout;
bool? originalPersistSecurityInfo = connInfo.ConnectionDetails.PersistSecurityInfo;
bool? originalPooling = connInfo.ConnectionDetails.Pooling;
// increase the connection timeout to at least 30 seconds and and build connection string
connInfo.ConnectionDetails.ConnectTimeout = Math.Max(30, originalTimeout ?? 0);
// enable PersistSecurityInfo to handle issues in SMO where the connection context is lost in reconnections
connInfo.ConnectionDetails.PersistSecurityInfo = true;
// turn off connection pool to avoid hold locks on server resources after calling SqlConnection Close method
connInfo.ConnectionDetails.Pooling = false;
connInfo.ConnectionDetails.ApplicationName = GetApplicationNameWithFeature(connInfo.ConnectionDetails.ApplicationName, featureName);
// generate connection string
string connectionString = BuildConnectionString(connInfo.ConnectionDetails);
// restore original values
connInfo.ConnectionDetails.ConnectTimeout = originalTimeout;
connInfo.ConnectionDetails.PersistSecurityInfo = originalPersistSecurityInfo;
connInfo.ConnectionDetails.Pooling = originalPooling;
// open a dedicated binding server connection
using (SqlConnection sqlConn = new SqlConnection(connectionString))
{
// Fill in Azure authentication token if needed
if (connInfo.ConnectionDetails.AzureAccountToken != null)
{
sqlConn.AccessToken = connInfo.ConnectionDetails.AzureAccountToken;
}
sqlConn.Open();
return sqlConn;
}
}
catch (Exception ex)
{
string error = string.Format(CultureInfo.InvariantCulture,
"Failed opening a SqlConnection: error:{0} inner:{1} stacktrace:{2}",
ex.Message, ex.InnerException != null ? ex.InnerException.Message : string.Empty, ex.StackTrace);
Logger.Write(TraceEventType.Error, error);
}
return null;
}
/// <summary>
/// Create and open a new SqlConnection from a ConnectionInfo object
/// Note: we need to audit all uses of this method to determine why we're
@@ -1445,23 +1387,6 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
return null;
}
/// <summary>
/// Create and open a new ServerConnection from a ConnectionInfo object.
/// This calls ConnectionService.OpenSqlConnection and then creates a
/// ServerConnection from it.
/// </summary>
/// <param name="connInfo">The connection info to connect with</param>
/// <param name="featureName">A plaintext string that will be included in the application name for the connection</param>
/// <returns>A ServerConnection (wrapping a SqlConnection) created with the given connection info</returns>
internal static ServerConnection OpenServerConnection(ConnectionInfo connInfo, string featureName = null)
{
SqlConnection sqlConnection = OpenSqlConnection(connInfo, featureName);
return connInfo.ConnectionDetails.AzureAccountToken != null
? new ServerConnection(sqlConnection, new AzureAccessToken(connInfo.ConnectionDetails.AzureAccountToken))
: new ServerConnection(sqlConnection);
}
public static void EnsureConnectionIsOpen(ReliableDataSourceConnection conn, bool forceReopen = false)
{
// verify that the connection is open

View File

@@ -202,15 +202,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
{
// disconnect existing connection
var bindingContext = this.BindingContextMap[key];
if (bindingContext.ServerConnection != null && bindingContext.ServerConnection.IsOpen)
{
// Disconnecting can take some time so run it in a separate task so that it doesn't block removal
Task.Run(() =>
{
bindingContext.ServerConnection.Cancel();
bindingContext.ServerConnection.Disconnect();
});
}
// remove key from the map
this.BindingContextMap.Remove(key);
@@ -483,17 +474,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
{
_itemQueuedEvent.Dispose();
}
if (this.BindingContextMap != null)
{
foreach (var item in this.BindingContextMap)
{
if (item.Value != null && item.Value.ServerConnection != null && item.Value.ServerConnection.SqlConnectionObject != null)
{
item.Value.ServerConnection.SqlConnectionObject.Close();
}
}
}
}
private bool IsExceptionOfType(Exception ex, Type t)

View File

@@ -3,14 +3,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Threading;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.SmoMetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.Common;
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.Kusto.ServiceLayer.DataSource;
namespace Microsoft.Kusto.ServiceLayer.LanguageServices
@@ -20,11 +13,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
/// </summary>
public class ConnectedBindingContext : IBindingContext
{
private ParseOptions parseOptions;
private ManualResetEvent bindingLock;
private ServerConnection serverConnection;
private readonly ManualResetEvent bindingLock;
/// <inheritdoc/>
public IDataSource DataSource { get; set; }
@@ -36,7 +25,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
{
this.bindingLock = new ManualResetEvent(initialState: true);
this.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout;
this.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
}
/// <summary>
@@ -44,39 +32,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
/// </summary>
public bool IsConnected { get; set; }
/// <summary>
/// Gets or sets the binding server connection
/// </summary>
public ServerConnection ServerConnection
{
get
{
return this.serverConnection;
}
set
{
this.serverConnection = value;
// reset the parse options so the get recreated for the current connection
this.parseOptions = null;
}
}
/// <summary>
/// Gets or sets the metadata display info provider
/// </summary>
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
/// <summary>
/// Gets or sets the SMO metadata provider
/// </summary>
public SmoMetadataProvider SmoMetadataProvider { get; set; }
/// <summary>
/// Gets or sets the binder
/// </summary>
public IBinder Binder { get; set; }
/// <summary>
/// Gets the binding lock object
/// </summary>
@@ -91,130 +46,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
/// <summary>
/// Gets or sets the binding operation timeout in milliseconds
/// </summary>
public int BindingTimeout { get; set; }
/// <summary>
/// Gets the Language Service ServerVersion
/// </summary>
public ServerVersion ServerVersion
{
get
{
return this.ServerConnection != null
? this.ServerConnection.ServerVersion
: null;
}
}
/// <summary>
/// Gets the current DataEngineType
/// </summary>
public DatabaseEngineType DatabaseEngineType
{
get
{
return this.ServerConnection != null
? this.ServerConnection.DatabaseEngineType
: DatabaseEngineType.Standalone;
}
}
/// <summary>
/// Gets the current connections TransactSqlVersion
/// </summary>
public TransactSqlVersion TransactSqlVersion
{
get
{
return this.IsConnected
? GetTransactSqlVersion(this.ServerVersion)
: TransactSqlVersion.Current;
}
}
/// <summary>
/// Gets the current DatabaseCompatibilityLevel
/// </summary>
public DatabaseCompatibilityLevel DatabaseCompatibilityLevel
{
get
{
return this.IsConnected
? GetDatabaseCompatibilityLevel(this.ServerVersion)
: DatabaseCompatibilityLevel.Current;
}
}
/// <summary>
/// Gets the current ParseOptions
/// </summary>
public ParseOptions ParseOptions
{
get
{
if (this.parseOptions == null)
{
this.parseOptions = new ParseOptions(
batchSeparator: LanguageService.DefaultBatchSeperator,
isQuotedIdentifierSet: true,
compatibilityLevel: DatabaseCompatibilityLevel,
transactSqlVersion: TransactSqlVersion);
}
return this.parseOptions;
}
}
/// <summary>
/// Gets the database compatibility level from a server version
/// </summary>
/// <param name="serverVersion"></param>
private static DatabaseCompatibilityLevel GetDatabaseCompatibilityLevel(ServerVersion serverVersion)
{
int versionMajor = Math.Max(serverVersion.Major, 8);
switch (versionMajor)
{
case 8:
return DatabaseCompatibilityLevel.Version80;
case 9:
return DatabaseCompatibilityLevel.Version90;
case 10:
return DatabaseCompatibilityLevel.Version100;
case 11:
return DatabaseCompatibilityLevel.Version110;
case 12:
return DatabaseCompatibilityLevel.Version120;
case 13:
return DatabaseCompatibilityLevel.Version130;
default:
return DatabaseCompatibilityLevel.Current;
}
}
/// <summary>
/// Gets the transaction sql version from a server version
/// </summary>
/// <param name="serverVersion"></param>
private static TransactSqlVersion GetTransactSqlVersion(ServerVersion serverVersion)
{
int versionMajor = Math.Max(serverVersion.Major, 9);
switch (versionMajor)
{
case 9:
case 10:
// In case of 10.0 we still use Version 10.5 as it is the closest available.
return TransactSqlVersion.Version105;
case 11:
return TransactSqlVersion.Version110;
case 12:
return TransactSqlVersion.Version120;
case 13:
return TransactSqlVersion.Version130;
default:
return TransactSqlVersion.Current;
}
}
public int BindingTimeout { get; set; }
}
}

View File

@@ -5,13 +5,8 @@
using System;
using System.Composition;
using Microsoft.SqlServer.Management.SmoMetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
using Microsoft.Kusto.ServiceLayer.Connection;
using Microsoft.Kusto.ServiceLayer.Connection.Contracts;
using Microsoft.Kusto.ServiceLayer.SqlContext;
using Microsoft.Kusto.ServiceLayer.Workspace;
using Microsoft.Kusto.ServiceLayer.DataSource;
namespace Microsoft.Kusto.ServiceLayer.LanguageServices
@@ -23,21 +18,11 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
public class ConnectedBindingQueue : BindingQueue<ConnectedBindingContext>, IConnectedBindingQueue
{
internal const int DefaultBindingTimeout = 500;
private readonly ISqlConnectionOpener _connectionOpener;
private readonly IDataSourceFactory _dataSourceFactory;
/// <summary>
/// Gets the current settings
/// </summary>
private SqlToolsSettings CurrentSettings
{
get { return WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings; }
}
[ImportingConstructor]
public ConnectedBindingQueue(ISqlConnectionOpener sqlConnectionOpener, IDataSourceFactory dataSourceFactory)
public ConnectedBindingQueue(IDataSourceFactory dataSourceFactory)
{
_connectionOpener = sqlConnectionOpener;
_dataSourceFactory = dataSourceFactory;
}
@@ -67,52 +52,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
return Uri.EscapeUriString(key);
}
/// <summary>
/// Generate a unique key based on the ConnectionInfo object
/// </summary>
/// <param name="serverName"></param>
/// <param name="databaseName"></param>
private string GetConnectionContextKey(string serverName, string databaseName)
{
return string.Format("{0}_{1}",
serverName ?? "NULL",
databaseName ?? "NULL");
}
public void CloseConnections(string serverName, string databaseName, int millisecondsTimeout)
{
string connectionKey = GetConnectionContextKey(serverName, databaseName);
var contexts = GetBindingContexts(connectionKey);
foreach (var bindingContext in contexts)
{
if (bindingContext.BindingLock.WaitOne(millisecondsTimeout))
{
bindingContext.ServerConnection.Disconnect();
}
}
}
public void OpenConnections(string serverName, string databaseName, int millisecondsTimeout)
{
string connectionKey = GetConnectionContextKey(serverName, databaseName);
var contexts = GetBindingContexts(connectionKey);
foreach (var bindingContext in contexts)
{
if (bindingContext.BindingLock.WaitOne(millisecondsTimeout))
{
try
{
bindingContext.ServerConnection.Connect();
}
catch
{
//TODO: remove the binding context?
}
}
}
}
public void RemoveBindingContext(ConnectionInfo connInfo)
{
string connectionKey = GetConnectionContextKey(connInfo.ConnectionDetails);
@@ -157,24 +96,10 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
try
{
bindingContext.BindingLock.Reset();
// populate the binding context to work with the SMO metadata provider
bindingContext.ServerConnection = _connectionOpener.OpenServerConnection(connInfo, featureName);
string connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails);
bindingContext.DataSource = _dataSourceFactory.Create(DataSourceType.Kusto, connectionString, connInfo.ConnectionDetails.AzureAccountToken);
if (needMetadata)
{
bindingContext.SmoMetadataProvider = SmoMetadataProvider.CreateConnectedProvider(bindingContext.ServerConnection);
bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
bindingContext.MetadataDisplayInfoProvider.BuiltInCasing =
this.CurrentSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value
? CasingStyle.Lowercase : CasingStyle.Uppercase;
bindingContext.Binder = BinderProvider.CreateBinder(bindingContext.SmoMetadataProvider);
}
bindingContext.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout;
bindingContext.BindingTimeout = DefaultBindingTimeout;
bindingContext.IsConnected = true;
}
catch (Exception)

View File

@@ -4,12 +4,6 @@
//
using System.Threading;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.SmoMetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.Common;
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.Kusto.ServiceLayer.DataSource;
@@ -25,31 +19,11 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
/// </summary>
bool IsConnected { get; set; }
/// <summary>
/// Gets or sets the binding server connection
/// </summary>
ServerConnection ServerConnection { get; set; }
/// <summary>
/// Gets or sets data source interface
/// </summary>
IDataSource DataSource { get; set; }
/// <summary>
/// Gets or sets the metadata display info provider
/// </summary>
MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
/// <summary>
/// Gets or sets the SMO metadata provider
/// </summary>
SmoMetadataProvider SmoMetadataProvider { get; set; }
/// <summary>
/// Gets or sets the binder
/// </summary>
IBinder Binder { get; set; }
/// <summary>
/// Gets the binding lock object
/// </summary>
@@ -59,30 +33,5 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
/// Gets or sets the binding operation timeout in milliseconds
/// </summary>
int BindingTimeout { get; set; }
/// <summary>
/// Gets or sets the current connection parse options
/// </summary>
ParseOptions ParseOptions { get; }
/// <summary>
/// Gets or sets the current connection server version
/// </summary>
ServerVersion ServerVersion { get; }
/// <summary>
/// Gets or sets the database engine type
/// </summary>
DatabaseEngineType DatabaseEngineType { get; }
/// <summary>
/// Gets or sets the T-SQL version
/// </summary>
TransactSqlVersion TransactSqlVersion { get; }
/// <summary>
/// Gets or sets the database compatibility level
/// </summary>
DatabaseCompatibilityLevel DatabaseCompatibilityLevel { get; }
}
}

View File

@@ -7,9 +7,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
public interface IConnectedBindingQueue
{
event BindingQueue<ConnectedBindingContext>.UnhandledExceptionDelegate OnUnhandledException;
void CloseConnections(string serverName, string databaseName, int millisecondsTimeout);
void OpenConnections(string serverName, string databaseName, int millisecondsTimeout);
string AddConnectionContext(ConnectionInfo connInfo, bool needMetadata, string featureName = null, bool overwrite = false);
void Dispose();
bool IsBindingContextConnected(string key);

View File

@@ -1,13 +0,0 @@
using Microsoft.Kusto.ServiceLayer.Connection;
using Microsoft.SqlServer.Management.Common;
namespace Microsoft.Kusto.ServiceLayer.LanguageServices
{
public interface ISqlConnectionOpener
{
/// <summary>
/// Virtual method used to support mocking and testing
/// </summary>
ServerConnection OpenServerConnection(ConnectionInfo connInfo, string featureName);
}
}

View File

@@ -1,15 +0,0 @@
using System.Composition;
using Microsoft.Kusto.ServiceLayer.Connection;
using Microsoft.SqlServer.Management.Common;
namespace Microsoft.Kusto.ServiceLayer.LanguageServices
{
[Export(typeof(ISqlConnectionOpener))]
public class SqlConnectionOpener : ISqlConnectionOpener
{
public ServerConnection OpenServerConnection(ConnectionInfo connInfo, string featureName)
{
return ConnectionService.OpenServerConnection(connInfo, featureName);
}
}
}

View File

@@ -412,19 +412,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
}
response.Nodes = nodes;
response.ErrorMessage = node.ErrorMessage;
try
{
// SMO changes the database when getting sql objects. Make sure the database is changed back to the original one
if (bindingContext.ServerConnection.CurrentDatabase != bindingContext.ServerConnection.DatabaseName)
{
bindingContext.ServerConnection.SqlConnectionObject.ChangeDatabase(bindingContext.ServerConnection.DatabaseName);
}
}
catch(Exception ex)
{
Logger.Write(TraceEventType.Warning, $"Failed to change the database in OE connection. error: {ex.Message}");
// We should just try to change the connection. If it fails, there's not much we can do
}
return response;
});

View File

@@ -66,9 +66,8 @@ namespace Microsoft.Kusto.ServiceLayer.UnitTests.LanguageServices
[Test]
public void AddConnectionContext_Returns_EmptyString_For_NullConnectionInfo()
{
var connectionOpenerMock = new Mock<ISqlConnectionOpener>();
var dataSourceFactory = new Mock<IDataSourceFactory>();
var connectedBindingQueue = new ConnectedBindingQueue(connectionOpenerMock.Object, dataSourceFactory.Object);
var connectedBindingQueue = new ConnectedBindingQueue(dataSourceFactory.Object);
var connectionKey = connectedBindingQueue.AddConnectionContext(null, false);
Assert.AreEqual(string.Empty, connectionKey);
@@ -81,9 +80,8 @@ namespace Microsoft.Kusto.ServiceLayer.UnitTests.LanguageServices
var connectionFactory = new Mock<IDataSourceConnectionFactory>();
var connectionInfo = new ConnectionInfo(connectionFactory.Object, "ownerUri", connectionDetails);
var connectionOpenerMock = new Mock<ISqlConnectionOpener>();
var dataSourceFactory = new Mock<IDataSourceFactory>();
var connectedBindingQueue = new ConnectedBindingQueue(connectionOpenerMock.Object, dataSourceFactory.Object);
var connectedBindingQueue = new ConnectedBindingQueue(dataSourceFactory.Object);
var connectionKey = connectedBindingQueue.AddConnectionContext(connectionInfo, false, "featureName");
Assert.AreEqual("NULL_NULL_NULL_NULL", connectionKey);
@@ -96,12 +94,6 @@ namespace Microsoft.Kusto.ServiceLayer.UnitTests.LanguageServices
var connectionFactory = new Mock<IDataSourceConnectionFactory>();
var connectionInfo = new ConnectionInfo(connectionFactory.Object, "ownerUri", connectionDetails);
var connectionOpenerMock = new Mock<ISqlConnectionOpener>();
var fakeServerConnection = new ServerConnection();
connectionOpenerMock
.Setup(x => x.OpenServerConnection(It.IsAny<ConnectionInfo>(), It.IsAny<string>()))
.Returns(fakeServerConnection);
var dataSourceFactory = new Mock<IDataSourceFactory>();
var dataSourceMock = new Mock<IDataSource>();
dataSourceFactory
@@ -109,18 +101,14 @@ namespace Microsoft.Kusto.ServiceLayer.UnitTests.LanguageServices
.Returns(dataSourceMock.Object);
var connectedBindingQueue =
new ConnectedBindingQueue(connectionOpenerMock.Object, dataSourceFactory.Object);
new ConnectedBindingQueue(dataSourceFactory.Object);
var connectionKey =
connectedBindingQueue.AddConnectionContext(connectionInfo, needsMetadata, "featureName");
var bindingContext = connectedBindingQueue.GetOrCreateBindingContext(connectionKey);
Assert.AreEqual(fakeServerConnection, bindingContext.ServerConnection);
Assert.AreEqual(dataSourceMock.Object, bindingContext.DataSource);
Assert.AreEqual(500, bindingContext.BindingTimeout);
Assert.AreEqual(true, bindingContext.IsConnected);
Assert.AreEqual(CasingStyle.Uppercase, bindingContext.MetadataDisplayInfoProvider.BuiltInCasing);
Assert.IsNull(bindingContext.SmoMetadataProvider);
Assert.IsNull(bindingContext.Binder);
}
[Test]
@@ -130,12 +118,10 @@ namespace Microsoft.Kusto.ServiceLayer.UnitTests.LanguageServices
var connectionFactory = new Mock<IDataSourceConnectionFactory>();
var connectionInfo = new ConnectionInfo(connectionFactory.Object, "ownerUri", connectionDetails);
var connectionOpenerMock = new Mock<ISqlConnectionOpener>();
var dataSourceFactory = new Mock<IDataSourceFactory>();
var connectedBindingQueue = new ConnectedBindingQueue(connectionOpenerMock.Object, dataSourceFactory.Object);
var connectedBindingQueue = new ConnectedBindingQueue(dataSourceFactory.Object);
var connectionKey = connectedBindingQueue.AddConnectionContext(connectionInfo, false, "featureName");
connectedBindingQueue.RemoveBindingContext(connectionInfo);
Assert.IsFalse(connectedBindingQueue.BindingContextMap.ContainsKey(connectionKey));