Files
sqltoolsservice/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/ServerNode.cs
Kevin Cunnane bbd0972dde Ensure connection open for OE queries and fix connection disposal (#359)
* Ensure connection open for OE queries and fix connection disposal
- Dispose connection in Metadata service, to ensure we cleanly dispose and don't rely on garbage colleciton
- Fixed issue where if the connection was closed, expanding databases in the Server would fail. This is because SMO doesn't always reopen the connection, certainly not for Server level queries. The solution is to always check if open and reopen.
- Added unit tests for this, which required mocking the relevant IsOpen / OpenConnection methods. Refactored SMO wrapper calls into a dedicated class file to handle this
2017-05-25 18:26:52 -07:00

191 lines
6.7 KiB
C#

//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Data.Common;
using System.Data.SqlClient;
using System.Globalization;
using System.Linq;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.Extensibility;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
{
/// <summary>
/// Server node implementation
/// </summary>
public class ServerNode : TreeNode
{
private ConnectionSummary connectionSummary;
private ServerInfo serverInfo;
private string connectionUri;
private Lazy<SmoQueryContext> context;
private ConnectionService connectionService;
private SmoWrapper smoWrapper;
private SqlServerType sqlServerType;
public ServerNode(ConnectionCompleteParams connInfo, IMultiServiceProvider serviceProvider)
: base()
{
Validate.IsNotNull(nameof(connInfo), connInfo);
Validate.IsNotNull("connInfo.ConnectionSummary", connInfo.ConnectionSummary);
Validate.IsNotNull(nameof(serviceProvider), serviceProvider);
this.connectionSummary = connInfo.ConnectionSummary;
this.serverInfo = connInfo.ServerInfo;
this.connectionUri = connInfo.OwnerUri;
this.sqlServerType = ServerVersionHelper.CalculateServerType(this.serverInfo);
this.connectionService = serviceProvider.GetService<ConnectionService>();
this.context = new Lazy<SmoQueryContext>(() => CreateContext(serviceProvider));
NodeValue = connectionSummary.ServerName;
IsAlwaysLeaf = false;
NodeType = NodeTypes.Server.ToString();
NodeTypeId = NodeTypes.Server;
Label = GetConnectionLabel();
}
internal SmoWrapper SmoWrapper
{
get
{
if (smoWrapper == null)
{
smoWrapper = new SmoWrapper();
}
return smoWrapper;
}
set
{
this.smoWrapper = value;
}
}
/// <summary>
/// Returns the label to display to the user.
/// </summary>
internal string GetConnectionLabel()
{
string userName = connectionSummary.UserName;
// TODO Domain and username is not yet supported on .Net Core.
// Consider passing as an input from the extension where this can be queried
//if (string.IsNullOrWhiteSpace(userName))
//{
// userName = Environment.UserDomainName + @"\" + Environment.UserName;
//}
// TODO Consider adding IsAuthenticatingDatabaseMaster check in the code and
// referencing result here
if (!ObjectExplorerUtils.IsSystemDatabaseConnection(connectionSummary.DatabaseName))
{
// We either have an azure with a database specified or a Denali database using a contained user
if (string.IsNullOrWhiteSpace(userName))
{
userName = connectionSummary.DatabaseName;
}
else
{
userName += ", " + connectionSummary.DatabaseName;
}
}
string label;
if (string.IsNullOrWhiteSpace(userName))
{
label = string.Format(
CultureInfo.InvariantCulture,
"{0} ({1} {2})",
connectionSummary.ServerName,
"SQL Server",
serverInfo.ServerVersion);
}
else
{
label = string.Format(
CultureInfo.InvariantCulture,
"{0} ({1} {2} - {3})",
connectionSummary.ServerName,
"SQL Server",
serverInfo.ServerVersion,
userName);
}
return label;
}
private SmoQueryContext CreateContext(IMultiServiceProvider serviceProvider)
{
string exceptionMessage;
ConnectionInfo connectionInfo;
SqlConnection connection = null;
// Get server object from connection
if (!connectionService.TryFindConnection(this.connectionUri, out connectionInfo) ||
connectionInfo.AllConnections == null || connectionInfo.AllConnections.Count == 0)
{
ErrorStateMessage = string.Format(CultureInfo.CurrentCulture,
SR.ServerNodeConnectionError, connectionSummary.ServerName);
return null;
}
//TODO: figure out how to use existing connections
DbConnection dbConnection = connectionInfo.AllConnections.First();
ReliableSqlConnection reliableSqlConnection = dbConnection as ReliableSqlConnection;
SqlConnection sqlConnection = dbConnection as SqlConnection;
if (reliableSqlConnection != null)
{
connection = reliableSqlConnection.GetUnderlyingConnection();
}
else if (sqlConnection != null)
{
connection = sqlConnection;
}
else
{
ErrorStateMessage = string.Format(CultureInfo.CurrentCulture,
SR.ServerNodeConnectionError, connectionSummary.ServerName);
return null;
}
try
{
Server server = SmoWrapper.CreateServer(connection);
return new SmoQueryContext(server, serviceProvider, SmoWrapper)
{
Parent = server,
SqlServerType = this.sqlServerType
};
}
catch (ConnectionFailureException cfe)
{
exceptionMessage = cfe.Message;
}
catch (Exception ex)
{
exceptionMessage = ex.Message;
}
Logger.Write(LogLevel.Error, "Exception at ServerNode.CreateContext() : " + exceptionMessage);
this.ErrorStateMessage = string.Format(SR.TreeNodeError, exceptionMessage);
return null;
}
public override object GetContext()
{
return context.Value;
}
}
}