创建 SQL Server Management Studio 12 插件






4.74/5 (14投票s)
在 SQL Server 12 中开发您自己的 SSMS 对象资源管理器\命令栏菜单
引言
和大多数 DBA 一样,我发现自己一遍又一遍地重复相同的 SQL 语句。肯定有更简单的方法可以提高 SQL 工作效率。因此,我决定使用 Visual Studio 13 开发自己的 SSMS (12) 自定义插件,而不是键入“SELECT COUNT(*) FROM XYZ_Table”。
背景
右键单击表即可获取行数,或者选择一列并查找该表中的重复值,或者从鼠标单击而不是键入、剪切\粘贴或从文件加载已保存的 SQL 查询并执行它们,就能针对 Master 数据库运行通用的常见 SQL 语句,这样难道不会更简单吗?
必备组件
- 已安装 Visual Studio 13(或 Express 版本)
- 已安装 SQL Server 2012(或 Express 版本)
- Visual Studio 2013 SDK(插件项目模板)
- 下载 Chinook SQL 脚本(SQLScript.zip)
我们要实现的目标
下面是插件将创建的自定义表上下文菜单(自定义 SQL & 脚本完整架构)的示例。您还将看到确定用户是右键单击表还是列的逻辑,并显示该对象的相应上下文菜单。下面是表的上下文菜单。对于列的上下文菜单,您可以找到该列中的重复值。
下面您可以看到,我为列添加了一个新的上下文菜单选项。在此示例中,我查找了特定列中的所有重复值。此示例将在稍后通过另一个屏幕截图以及 SQL\代码的解释来解释。
我们还将创建菜单命令来执行全局 SQL 语句。例如,下面我们正在使用“工具”菜单执行“终止事务”SQL 语句。我的 SQL 设计方式是,它不会完全执行 SQL,但有一些注释掉的行。您可以取消注释并在对 SQL 满意后运行它们。
创建您的插件项目
在获得插件项目的骨架之前,需要经过几个屏幕。下面您可以看到,大多数情况下您都可以默认完成。只需确保安装 Visual Studio 13 SDK,因为它包含了 Visual Studio 13 中使用的新插件项目模板。
项目设置和部署注意事项
SSMS 版本之间的重大更改
过去,当您必须为 SSMS 2005 开发插件,然后为 SSMS 20088\R2 开发相同的插件代码时,您的代码将无法编译,因为许多属性\方法已被弃用且不向后兼容。在 SSMS 12+ 中,Microsoft 已(尽管使用的是 Visual Studio 10)重新设计了插件背后的对象。从而使其更具前瞻性兼容性……我对此持保留态度,但冲突应该会大大减少。
部署文件夹
当您想在 SQL Server 中测试您的插件时,您需要让 SQL Server 知道它需要在启动时附加插件。为此,只需将您的 .Addin
文件复制到以下路径之一。下面的选项 2 将为所有 SQL Server 用户附加插件。如果以下文件夹不存在,则只需创建文件夹结构。
- C:\Users\UserName\AppData\Roaming\Microsoft\MSEnvShared\Addins
- C:\ProgramData\Microsoft\MicrosoftSQL Server Management Studio\11.0\Addins
程序集引用
我认为最麻烦的区域是程序集。因为存在名称相似且版本冲突的程序集。但下面的列表应该涵盖您的大部分插件任务。您可以在项目中看到程序集的路径(程序集的属性),并以此为指南在您的环境中查找程序集。
项目属性
右键单击您的项目,然后从上下文菜单中选择属性。将启动您的插件的外部应用程序设置为您的SQL Server Management Studio可执行文件的路径。大约是
C:\Program Files (x86)\Microsoft SQL Server\110\Tools\Binn\ManagementStudio\Ssms.exe
删除“命令行参数”和“工作目录”条目。
配置插件文件 XML
在部署或测试您的插件之前,您需要对其进行配置。基本上,您只需要更新三个元素。
- 名称 - 将此更改为Microsoft SQL Server Management Studio
- 版本 - 插入一个星号,这将涵盖各种 SQL Server 版本(因为会有 CTP 版本等)
- 程序集 - 输入您的插件程序集的路径(测试时为 debug,准备部署时为 release)
调试时关闭 PInvoke 异常
调试时,PInvoke 错误会弹出,这在运行时不是问题,而是因为 C++ 和 C# 之间的 Long
和 Int
转换导致冲突(仅限调试)。因此,要停止弹出,只需在 Visual Studio 中关闭 PInvoke。
点击 Debug.Exceptions
,然后向下滚动到 Pinvoke Stack Imbalance
并取消选中它。
项目结构
该插件具有非常简单的项目结构,一个主(connect.cs)类,一个控制器类,两个菜单项类以及我们将部署到 Addin 文件夹的 SSMSAdin.addin XML 文件。有一个部分类用于分离事件,而不是弄乱主 Connect.cs 类,因为我们将只使用几个主要的事件。
代码解释
创建对象资源管理器上下文 & 命令栏菜单项
您将实现的 IDTExtensibility2
成员方法之一是 OnConnection
。这是插件的起点。下面的代码片段创建了新的 Tools
菜单项,以及一个事件处理程序,用于在对象资源管理器操作更改时(即单击表或列 - 此事件将捕获该操作),从而允许您控制特定对象资源管理器树项应显示的上下文菜单。
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
applicationObject = (DTE2)application;
addInInstance = (AddIn)addInInst;
try
{
ContextService contextService = (ContextService)ServiceCache.ServiceProvider.GetService(typeof(IContextService));
contextService.ActionContext.CurrentContextChanged += ActionContextOnCurrentContextChanged;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
if (connectMode == ext_ConnectMode.ext_cm_UISetup)
{
object[] contextGUIDS = new object[] { };
Commands2 commands = (Commands2)applicationObject.Commands;
string toolsMenuName = "Tools";
//Find the MenuBar command bar, which is the top-level command bar holding all the main menu items:
Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)applicationObject.CommandBars)["MenuBar"];
//Find the Tools command bar on the MenuBar command bar:
CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;
//This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in,
// just make sure you also update the QueryStatus/Exec method to include the new command names.
try
{
//Add a command to the Commands collection:
cmdKillTrans = commands.AddNamedCommand2(addInInstance, "KillTrans", "Kill Transactions", "List all transactions that can be stopped", true, 9, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
//Add a control for the command to the tools menu:
if ((cmdKillTrans != null) && (toolsPopup != null))
{
cmdKillTrans.AddControl(toolsPopup.CommandBar, 1);
}
}
catch (System.ArgumentException ex)
{
MessageBox.Show(ex.Message);
DebugMessage(ex.Message);
}
//This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in,
// just make sure you also update the QueryStatus/Exec method to include the new command names.
try
{
//Add a command to the Commands collection:
cmdServerInfo = commands.AddNamedCommand2(addInInstance, "ServerInfo", "Server Information", "List server\\product information", true, 5, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
//Add a control for the command to the tools menu:
if ((cmdServerInfo != null) && (toolsPopup != null))
{
cmdServerInfo.AddControl(toolsPopup.CommandBar, 2);
}
}
catch (System.ArgumentException ex)
{
MessageBox.Show(ex.Message);
DebugMessage(ex.Message);
}
//This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in,
// just make sure you also update the QueryStatus/Exec method to include the new command names.
try
{
//Add a command to the Commands collection:
commandBackUp = commands.AddNamedCommand2(addInInstance, "BackupInfo", "Backup Information", "List backup information", true, 8, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
//Add a control for the command to the tools menu:
if ((commandBackUp != null) && (toolsPopup != null))
{
commandBackUp.AddControl(toolsPopup.CommandBar, 3);
}
}
catch (System.ArgumentException ex)
{
MessageBox.Show(ex.Message);
DebugMessage(ex.Message);
}
}
}
对象资源管理器事件处理程序
下面的事件在上面的 OnConnection 方法中设置,因此我们可以捕获每个对象资源管理器的更改。我们利用 static
变量来防止上下文菜单项被创建多次。
注意:UserTables
是表的 InvariantName
。
private void ActionContextOnCurrentContextChanged(object sender, EventArgs e)
{
try
{
INodeInformation[] nodes;
INodeInformation node;
int nodeCount;
IObjectExplorerService objectExplorer = (ObjectExplorerService)ServiceCache.ServiceProvider.GetService(typeof(IObjectExplorerService));
objectExplorer.GetSelectedNodes(out nodeCount, out nodes);
node = nodeCount > 0 ? nodes[0] : null;
if (node != null)
{
if (node.Parent.InvariantName == "UserTables")
{
if (!IsTableMenuAdded)
{
_tableMenu = (HierarchyObject)node.GetService(typeof(IMenuHandler));
SqlTableMenuItem item = new SqlTableMenuItem(applicationObject);
_tableMenu.AddChild(string.Empty, item);
IsTableMenuAdded = true;
}
}
else if (node.Parent.InvariantName == "Columns")
{
if (!IsColumnMenuAdded)
{
_tableMenu = (HierarchyObject)node.GetService(typeof(IMenuHandler));
SqlColumnMenuItem item = new SqlColumnMenuItem(applicationObject);
_tableMenu.AddChild(string.Empty, item);
IsColumnMenuAdded = true;
}
}
}
}
catch (Exception ObjectExplorerContextException)
{
MessageBox.Show(ObjectExplorerContextException.Message);
}
}
断开连接
断开与 SQL Server 的连接时,我们需要进行清理并删除新添加的菜单项。当 SQL Server 启动时,它们将被重新加载,因为 SQL Server 会在 Addin 文件夹中查找。
我们删除它们是因为,您可以手动进入并删除 Addin 文件,而它们仍会留在 SQL Server 中(永远!!!)。
public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
{
switch (disconnectMode)
{
case ext_DisconnectMode.ext_dm_HostShutdown:
case ext_DisconnectMode.ext_dm_UserClosed:
if ((cmdServerInfo != null))
{
cmdServerInfo.Delete();
}
if ((commandBackUp != null))
{
commandBackUp.Delete();
}
if ((cmdKillTrans != null))
{
cmdKillTrans.Delete();
}
break;
}
}
上下文菜单点击事件
重复上下文菜单项的点击事件将检索数据库、表和列名,并构建 SQL 语句。在新的查询窗口中显示它们并执行。
private void Duplicate_Click(object sender, EventArgs e)
{
ToolStripMenuItem item = (ToolStripMenuItem)sender;
bool generateColumnNames = (bool)item.Tag;
Match match = columnRegex.Match(this.Parent.Context);
if (match != null)
{
string columnName = match.Groups["Column"].Value;
string tableName = match.Groups["Table"].Value;
string schema = match.Groups["Schema"].Value;
string database = match.Groups["Database"].Value;
string connectionString = this.Parent.Connection.ConnectionString + ";Database=" + database;
string sqlStatement = string.Format(SSMSAddin.Properties.Resources.SQLDuplicateColumnData, columnName, tableName);
this.dteController.CreateNewScriptWindow(new StringBuilder(sqlStatement)); // create new document
this.applicationObject.ExecuteCommand("Query.Execute"); // get query analyzer window to execute query
}
}
我们可以看到确实存在重复的艺术家,如果我们从表中进行 select - 当我们执行上述重复查询时,它会留下一个唯一的艺术家。
在 SQL Server 12 中测试\运行插件
由于我们将 SSMS 设置为要测试的外部应用程序(项目属性),SSMS 将自动启动,我们可以单步执行(F10)调试器。
注意: 记住将您的 .Addin
文件复制到部署文件夹之一,并且 XML 元素 Assembly
引用我们的 Debug
程序集。
下面是我们创建的每个菜单项的示例。
表上下文菜单
表计数
生成插入脚本
列上下文菜单
正如我们在上一节中看到的,有一个列上下文菜单项。请注意注释掉的行,当您对执行 SQL 语句有信心时,可以取消注释这些行。
全局 - 命令栏菜单
从“工具”菜单中,我们可以看到新创建的(全局作用)SQL 语句。我在其中包含了一些常见的 SQL 语句,例如终止事务、服务器信息和显示备份信息。
下面,我执行了服务器信息菜单项,这将提供有关您的 SQL Server 的一般信息。
SQL Server 12 版本详情
下面是我用于开发插件的 SQL Server 12 的版本详情(足够标准)。
关注点
既然您已经看到了如何引用表或列,那么扩展您的自定义插件以执行更复杂的 SQL 语句就相对容易了。例如,当选择两个列时,可以选择基于连接(因为您可以执行 SQL 语句来查找 FK 并动态构建 SQL)的所有内容。
有用链接
- EnvDTE 命名空间
- https://sqlbits.com/Sessions/Event9/Building_a_SSMS_Add-in_The_Agony_and_Ecstasy
- https://ssmsaddin2012.codeplex.com/SourceControl/latest
- http://www.ssmsboost.com/create-own-ssms-2012-add-in-sample-code-with-download
- http://blogs.microsoft.co.il/shair/2008/07/28/how-to-create-sql-server-management-studio-addin/
- http://tsqltidy.blogspot.co.uk/2011/08/how-to-write-sql-server-management.html