提问人:Joe Obbish 提问时间:8/31/2022 更新时间:1/22/2023 访问量:713
如何创建从执行计划窗口打开的 SSMS 扩展?
How can I create an SSMS extension that opens from the execution plan window?
问:
我想创建一个从执行计划窗口打开的 SQL Server Management Studio v18 扩展。我相信这在技术上是可行的,因为有一个第三方工具已经做到了这一点:
到目前为止,我已经能够使用此处的指南在 SSMS v18 中创建基本扩展。我还能够通过引用文档中的 ID 来移动按钮的位置。但是,我无法弄清楚如何修改 .vsct 文件以将我的按钮移动到执行计划窗口中。
如何创建从执行计划窗口打开的 SSMS 扩展?
答:
5赞
Martin Smith
1/9/2023
#1
我想通了。
在 *.vsct 文件的元素中,添加<Symbols>
<GuidSymbol name="foo1" value="{33F13AC3-80BB-4ECB-85BC-225435603A5E}">
<IDSymbol name="foo2" value="0x0080"/>
</GuidSymbol>
然后改变
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
自
<Parent guid="foo1" id="foo2"/>
如此处所述。
(您可能还希望将 on 父元素设置为 if eager to see your extension in the menu higher)priority
<Group
0x0001
我确定魔术 Guid 的机制最初非常费力,并且在此答案的编辑历史记录中,但一种不那么费力的方法是设置注册表项
[HKEY_CURRENT_USER\SOFTWARE\Microsoft\SQL Server Management Studio\18.0_IsoShell\General]
"EnableVSIPLogging"=dword:00000001
然后打开 SSMS,获取执行计划,然后单击 + + 右键以显示一个消息框,如下所示(小数点后 128 位)。CtrlShift0x80
现在怎么办?
对我来说,如何在菜单单击事件中做任何有用的事情并不明显,所以我认为添加一个示例是有益的。
作为 POC,我尝试对子树成本高于阈值和/或底层执行计划运算符满足一些潜在问题条件的节点进行着色。
为此,我将模板生成代码中方法中的代码更改为Execute
private void Execute(object sender, EventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();
Dictionary<string, Color> coloringRules = new Dictionary<string, Color>
{
[@".//ns:Intrinsic[@FunctionName = ""GetRangeThroughConvert""]"] = Color.Yellow,
[@".//ns:Intrinsic[@FunctionName = ""GetRangeWithMismatchedTypes""]"] = Color.MediumPurple
};
ProofOfConcept.ColorInterestingNodes(coloringRules, costThreshold: 0.0032831);
}
这依赖于以下嵌套类
private class ProofOfConcept
{
private const string ShowPlanControlTypeFullName = "Microsoft.SqlServer.Management.UI.VSIntegration.Editors.ShowPlan.ShowPlanControl";
private const string GraphControlTypeFullName = "Microsoft.SqlServer.Management.SqlMgmt.ShowPlan.GraphControl";
private const string ShowPlanNamespaceUri = "http://schemas.microsoft.com/sqlserver/2004/07/showplan";
private static readonly XNamespace ns = ShowPlanNamespaceUri;
[DllImport("user32.dll")]
public static extern IntPtr GetFocus();
public static void ColorInterestingNodes(Dictionary<string, Color> coloringRules, double costThreshold)
{
IntPtr focus = GetFocus();
Control activeControl = Control.FromChildHandle(focus);
Control rootControl = FindRootControl(activeControl);
List <Control> graphControls = new List<Control>();
FindAllDescendantControlsOfType(rootControl, graphControls, GraphControlTypeFullName);
XElement[] qpElements = GetShowPlanXMLQueryPlans(rootControl);
//TODO: More robust method of matching up the query plan XML elements with the display elements.
//e.g. "Use database;" statement will show a graph in "estimated" plan but not "actual" - and not have a QueryPlan element in the XML
if (graphControls.Count != qpElements.Count())
{
MessageBox.Show("Mismatch between graph control count (" + graphControls.Count + ") and query plan count (" + qpElements.Count() + "). Exiting");
return;
}
for (var index = 0; index < graphControls.Count; index++)
{
Control graphControl = graphControls[index];
XElement qpElement = qpElements[index];
Dictionary<int, Color> nodeBackgroundColors = GetNodeBackgroundColors(qpElement, coloringRules);
foreach (dynamic item in ((dynamic)graphControl).Nodes)
{
var nodeId = item.NodeOriginal["NodeId"] ?? -1;
if (item.NodeOriginal.Cost >= costThreshold)
{
item.TextColor = Color.Red;
item.BackgroundColor = Color.White;
item.UseBackgroundColor = true;
}
if (nodeBackgroundColors.TryGetValue(nodeId, out Color color))
{
item.BackgroundColor = color;
item.UseBackgroundColor = true;
}
}
graphControl.Refresh();
}
}
private static Dictionary<int, Color> GetNodeBackgroundColors(XElement queryPlan, Dictionary<string, Color> coloringRules)
{
var returnValue = new Dictionary<int, Color>();
NameTable nt = new NameTable();
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(nt);
namespaceManager.AddNamespace("ns", "http://schemas.microsoft.com/sqlserver/2004/07/showplan");
foreach (var coloringRule in coloringRules)
{
var foundElements = queryPlan.XPathSelectElements(coloringRule.Key, namespaceManager);
foreach (var foundNode in foundElements)
{
var nodeId = foundNode.AncestorsAndSelf(ns + "RelOp").FirstOrDefault()?.Attribute("NodeId")?.Value;
if (nodeId != null)
{
returnValue[int.Parse(nodeId)] = coloringRule.Value;
}
}
}
return returnValue;
}
private static XElement[] GetShowPlanXMLQueryPlans(Control rootControl)
{
List<Control> showPlanControls = new List<Control>();
FindAllDescendantControlsOfType(rootControl, showPlanControls, ShowPlanControlTypeFullName);
Assembly sqlEditorsAssembly = Assembly.Load("SQLEditors");
Type showPlanControlType = sqlEditorsAssembly.GetType(ShowPlanControlTypeFullName);
MethodInfo GetShowPlanXmlMethod = showPlanControlType.GetMethod("GetShowPlanXml", BindingFlags.Instance | BindingFlags.NonPublic);
string xplan = GetShowPlanXmlMethod.Invoke(showPlanControls[0], null) as string;
XDocument doc = XDocument.Parse(xplan);
return doc.Descendants(ns + "QueryPlan").ToArray();
}
private static Control FindRootControl(Control control)
{
while (control.Parent != null)
control = control.Parent;
return control;
}
private static void FindAllDescendantControlsOfType(Control control, List<Control> graphControls, string typeFullName)
{
if (control.GetType().FullName == typeFullName)
graphControls.Add(control);
foreach (Control child in control.Controls)
FindAllDescendantControlsOfType(child, graphControls, typeFullName);
}
}
评论