/// <summary> /// Open a connection with the specified connection details /// </summary> /// <param name="connectionParams"></param> public async Task <ConnectionCompleteParams> Connect(ConnectParams connectionParams) { // Validate parameters string paramValidationErrorMessage; if (connectionParams == null) { return(new ConnectionCompleteParams { Messages = SR.ConnectionServiceConnectErrorNullParams }); } if (!connectionParams.IsValid(out paramValidationErrorMessage)) { return(new ConnectionCompleteParams { OwnerUri = connectionParams.OwnerUri, Messages = paramValidationErrorMessage }); } // Resolve if it is an existing connection // Disconnect active connection if the URI is already connected ConnectionInfo connectionInfo; if (ownerToConnectionMap.TryGetValue(connectionParams.OwnerUri, out connectionInfo)) { var disconnectParams = new DisconnectParams() { OwnerUri = connectionParams.OwnerUri }; Disconnect(disconnectParams); } connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); // try to connect var response = new ConnectionCompleteParams(); response.OwnerUri = connectionParams.OwnerUri; CancellationTokenSource source = null; try { // build the connection string from the input parameters string connectionString = ConnectionService.BuildConnectionString(connectionInfo.ConnectionDetails); // create a sql connection instance connectionInfo.SqlConnection = connectionInfo.Factory.CreateSqlConnection(connectionString); // turning on MARS to avoid break in LanguageService with multiple editors // we'll remove this once ConnectionService is refactored to not own the LanguageService connection connectionInfo.ConnectionDetails.MultipleActiveResultSets = true; // Add a cancellation token source so that the connection OpenAsync() can be cancelled using (source = new CancellationTokenSource()) { // Locking here to perform two operations as one atomic operation lock (cancellationTokenSourceLock) { // If the URI is currently connecting from a different request, cancel it before we try to connect CancellationTokenSource currentSource; if (ownerToCancellationTokenSourceMap.TryGetValue(connectionParams.OwnerUri, out currentSource)) { currentSource.Cancel(); } ownerToCancellationTokenSourceMap[connectionParams.OwnerUri] = source; } // Create a task to handle cancellation requests var cancellationTask = Task.Run(() => { source.Token.WaitHandle.WaitOne(); try { source.Token.ThrowIfCancellationRequested(); } catch (ObjectDisposedException) { // Ignore } }); var openTask = Task.Run(async() => { await connectionInfo.SqlConnection.OpenAsync(source.Token); }); // Open the connection await Task.WhenAny(openTask, cancellationTask).Unwrap(); source.Cancel(); } } catch (SqlException ex) { response.ErrorNumber = ex.Number; response.ErrorMessage = ex.Message; response.Messages = ex.ToString(); return(response); } catch (OperationCanceledException) { // OpenAsync was cancelled response.Messages = SR.ConnectionServiceConnectionCanceled; return(response); } catch (Exception ex) { response.ErrorMessage = ex.Message; response.Messages = ex.ToString(); return(response); } finally { // Remove our cancellation token from the map since we're no longer connecting // Using a lock here to perform two operations as one atomic operation lock (cancellationTokenSourceLock) { // Only remove the token from the map if it is the same one created by this request CancellationTokenSource sourceValue; if (ownerToCancellationTokenSourceMap.TryGetValue(connectionParams.OwnerUri, out sourceValue) && sourceValue == source) { ownerToCancellationTokenSourceMap.TryRemove(connectionParams.OwnerUri, out sourceValue); } } } ownerToConnectionMap[connectionParams.OwnerUri] = connectionInfo; // Update with the actual database name in connectionInfo and result // Doing this here as we know the connection is open - expect to do this only on connecting connectionInfo.ConnectionDetails.DatabaseName = connectionInfo.SqlConnection.Database; response.ConnectionSummary = new ConnectionSummary() { ServerName = connectionInfo.ConnectionDetails.ServerName, DatabaseName = connectionInfo.ConnectionDetails.DatabaseName, UserName = connectionInfo.ConnectionDetails.UserName, }; // invoke callback notifications invokeOnConnectionActivities(connectionInfo); // try to get information about the connected SQL Server instance try { var reliableConnection = connectionInfo.SqlConnection as ReliableSqlConnection; DbConnection connection = reliableConnection != null?reliableConnection.GetUnderlyingConnection() : connectionInfo.SqlConnection; ReliableConnectionHelper.ServerInfo serverInfo = ReliableConnectionHelper.GetServerVersion(connection); response.ServerInfo = new Contracts.ServerInfo() { ServerMajorVersion = serverInfo.ServerMajorVersion, ServerMinorVersion = serverInfo.ServerMinorVersion, ServerReleaseVersion = serverInfo.ServerReleaseVersion, EngineEditionId = serverInfo.EngineEditionId, ServerVersion = serverInfo.ServerVersion, ServerLevel = serverInfo.ServerLevel, ServerEdition = serverInfo.ServerEdition, IsCloud = serverInfo.IsCloud, AzureVersion = serverInfo.AzureVersion, OsVersion = serverInfo.OsVersion }; } catch (Exception ex) { response.Messages = ex.ToString(); } // return the connection result response.ConnectionId = connectionInfo.ConnectionId.ToString(); return(response); }