Update connection logic to handle multiple connections per URI (#176)

* Add CancelTokenKey for uniquely identifying cancelations of Connections associated with an OwnerUri and ConnectionType string.

* Update ConnectionInfo to use ConcurrentDictionary of DbConnection instances. Add wrapper functions for the ConcurrentDictionary. 

* Refactor Connect and Disconnect in ConnectionService.

* Update ConnectionService: Handle multiple connections per ConnectionInfo. Handle cancelation tokens uniquely identified with CancelTokenKey. Add GetOrOpenConnection() for other services to request an existing or create a new DbConnection.

* Add ConnectionType.cs for ConnectionType strings.

* Add ConnectionType string to ConnectParams, ConnectionCompleteNotification, DisconnectParams.

* Update Query ExecuteInternal to use the dedicated query connection and GetOrOpenConnection().

* Update test library to account for multiple connections in ConnectionInfo.

* Write tests ensuring multiple connections don’t create redundant data.

* Write tests ensuring database changes affect all connections of a given ConnectionInfo.

* Write tests for TRANSACTION statements and temp tables.
This commit is contained in:
Connor Quagliana
2017-01-18 17:59:41 -08:00
committed by GitHub
parent 31e1422a0e
commit c055c459a0
17 changed files with 1005 additions and 192 deletions

View File

@@ -4,8 +4,12 @@
//
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Connection
{
@@ -23,7 +27,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
OwnerUri = ownerUri;
ConnectionDetails = details;
ConnectionId = Guid.NewGuid();
IntellisenseMetrics = new InteractionMetrics<double>(new int[] { 50, 100, 200, 500, 1000, 2000 });
IntellisenseMetrics = new InteractionMetrics<double>(new int[] {50, 100, 200, 500, 1000, 2000});
}
/// <summary>
@@ -39,17 +43,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
/// <summary>
/// Factory used for creating the SQL connection associated with the connection info.
/// </summary>
public ISqlConnectionFactory Factory {get; private set;}
public ISqlConnectionFactory Factory { get; private set; }
/// <summary>
/// Properties used for creating/opening the SQL connection.
/// </summary>
public ConnectionDetails ConnectionDetails { get; private set; }
/// <summary>
/// The connection to the SQL database that commands will be run against.
/// A map containing all connections to the database that are associated with
/// this ConnectionInfo's OwnerUri.
/// This is internal for testing access only
/// </summary>
public DbConnection SqlConnection { get; set; }
internal readonly ConcurrentDictionary<string, DbConnection> ConnectionTypeToConnectionMap =
new ConcurrentDictionary<string, DbConnection>();
/// <summary>
/// Intellisense Metrics
@@ -60,8 +67,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
/// Returns true is the db connection is to a SQL db
/// </summary>
public bool IsAzure { get; set; }
/// <summary>
/// Returns true if the sql connection is to a DW instance
/// </summary>
public bool IsSqlDW { get; set; }
@@ -71,5 +77,86 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
/// </summary>
public int MajorVersion { get; set; }
/// <summary>
/// All DbConnection instances held by this ConnectionInfo
/// </summary>
public ICollection<DbConnection> AllConnections
{
get
{
return ConnectionTypeToConnectionMap.Values;
}
}
/// <summary>
/// All connection type strings held by this ConnectionInfo
/// </summary>
/// <returns></returns>
public ICollection<string> AllConnectionTypes
{
get
{
return ConnectionTypeToConnectionMap.Keys;
}
}
/// <summary>
/// The count of DbConnectioninstances held by this ConnectionInfo
/// </summary>
public int CountConnections
{
get
{
return ConnectionTypeToConnectionMap.Count;
}
}
/// <summary>
/// Try to get the DbConnection associated with the given connection type string.
/// </summary>
/// <returns>true if a connection with type connectionType was located and out connection was set,
/// false otherwise </returns>
/// <exception cref="ArgumentException">Thrown when connectionType is null or empty</exception>
public bool TryGetConnection(string connectionType, out DbConnection connection)
{
Validate.IsNotNullOrEmptyString("Connection Type", connectionType);
return ConnectionTypeToConnectionMap.TryGetValue(connectionType, out connection);
}
/// <summary>
/// Adds a DbConnection to this object and associates it with the given
/// connection type string. If a connection already exists with an identical
/// connection type string, it is not overwritten. Ignores calls where connectionType = null
/// </summary>
/// <exception cref="ArgumentException">Thrown when connectionType is null or empty</exception>
public void AddConnection(string connectionType, DbConnection connection)
{
Validate.IsNotNullOrEmptyString("Connection Type", connectionType);
ConnectionTypeToConnectionMap.TryAdd(connectionType, connection);
}
/// <summary>
/// Removes the single DbConnection instance associated with string connectionType
/// </summary>
/// <exception cref="ArgumentException">Thrown when connectionType is null or empty</exception>
public void RemoveConnection(string connectionType)
{
Validate.IsNotNullOrEmptyString("Connection Type", connectionType);
DbConnection connection;
ConnectionTypeToConnectionMap.TryRemove(connectionType, out connection);
}
/// <summary>
/// Removes all DbConnection instances held by this object
/// </summary>
public void RemoveAllConnections()
{
foreach (var type in AllConnectionTypes)
{
DbConnection connection;
ConnectionTypeToConnectionMap.TryRemove(type, out connection);
}
}
}
}