Returning Values From Query Plans Using C#
To return the query plan you can turn on the showplan_xml statement to return detailed information on how a particular batch would be executed without actually executing it. It would be better to turn on SHOWPLAN_XML and execute on a per batch basis to make the process of navigating through the query plan easier. This is the code to return the query plan:
using System;
using System.Data.SqlClient;
namespace UnitTestFrameWork
{
public class GetQueryPlan
{
public string GetQueryPlanForQuery(string conn, string batch)
{
SqlConnection _conn = new SqlConnection(conn);
_conn.Open();
SqlCommand cmd = _conn.CreateCommand();
cmd.CommandText = "SET SHOWPLAN_XML ON";
cmd.ExecuteNonQuery();
cmd.CommandText = batch;
try
{
String QueryPlan = String.Empty;
SqlDataReader sdr = cmd.ExecuteReader();
while (sdr.Read()) QueryPlan += sdr.GetSqlString(0).ToString();
sdr.Close();
cmd.CommandText = "SET SHOWPLAN_XML OFF";
cmd.ExecuteNonQuery();
return QueryPlan;
}
catch (SqlException)
{
string e = "Query plan could not be returned.";
return e;
}
}
}
}
using System;
using System.Xml;
using System.Xml.XPath;
using System.IO;
namespace UnitTestFrameWork
{
public class GetQueryPlanValues
{
public XPathNodeIterator GetXpathIterator(string QueryPlan, string SqlNamespace, string xpath)
{
//read query plan to tring, then xml reader
StringReader strReader = new StringReader(QueryPlan);
XmlTextReader xreader = new XmlTextReader(strReader);
//xpath document from the xreader, preserving whitespace,
//then a nvaigator so that we can search using xpath
XPathDocument doc = new XPathDocument(xreader, XmlSpace.Preserve);
XPathNavigator navigator = doc.CreateNavigator();
//define namespace
XmlNamespaceManager nsmgr = new XmlNamespaceManager(navigator.NameTable);
nsmgr.AddNamespace("sql", SqlNamespace);
//create xpath expression to search
XPathExpression xpression;
xpression = navigator.Compile(xpath);
//set namespace in the expression
xpression.SetContext(nsmgr);
//iterate over the nodes
XPathNodeIterator iterator = navigator.Select(xpression);
return iterator;
}
public string GetSumOfValues(string QueryPlan, string SqlNamespace, string xpath)
{
try
{
string _QueryPlan = QueryPlan;
string _SqlNameSpace = SqlNamespace;
string _xpath = xpath;
XPathNodeIterator i = GetXpathIterator(_QueryPlan, _SqlNameSpace, _xpath);
//get total value of each of the expressions we are looking for in the node
Single TotalValue = 0;
while (i.MoveNext()) TotalValue += Single.Parse(i.Current.Value);
string ret = String.Empty;
ret = Convert.ToString(TotalValue);
return ret;
}
catch (Exception e)
{
string error = e.Message;
return "-1";
}
}
public string GetSingleValue(string QueryPlan, string SqlNamespace, string xpath)
{
try
{
string _QueryPlan = QueryPlan;
string _SqlNameSpace = SqlNamespace;
string _xpath = xpath;
XPathNodeIterator i = GetXpathIterator(_QueryPlan, _SqlNameSpace, _xpath);
string ret = String.Empty;
while (i.MoveNext()) ret = i.Current.Value;
return ret;
}
catch (Exception e)
{
string error = e.Message;
return "-1";
}
}
}
}
USE NORTHWND
GO
SELECT * FROM dbo.Categories c
GO
SELECT c.CategoryName, s.CompanyName, s.ContactName, s.Phone, p.ProductName FROM dbo.[Order Details] od
INNER JOIN dbo.Products p ON p.ProductID = od.ProductID
INNER JOIN dbo.Suppliers s ON s.SupplierID = p.SupplierID
INNER JOIN dbo.Categories c ON c.CategoryID = p.CategoryID
WHERE c.CategoryName = 'Condiments'
AND p.Discontinued = 0
GROUP BY c.CategoryName, s.CompanyName, s.ContactName, p.ProductName,s.Phone
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Data;
using System.IO;
using System.Text.RegularExpressions;
using System.Data.SqlClient;
using QueryPlanFrameWork;
namespace DemoUnitTest
{
[TestClass]
public class RunTest
{
[TestMethod]
public void RunTestScriptTestMethod()
{
GetQueryPlan qp = new GetQueryPlan();
GetQueryPlanValues qv = new GetQueryPlanValues();
string constring_1 = "Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=NORTHWND;Data Source=.";
string fileContent = File.ReadAllText(@"..\\..\\TestScript\\TestScript.sql");
string[] batches = Regex.Split(fileContent, "GO");
string SqlNamespace = "http://schemas.microsoft.com/sqlserver/2004/07/showplan";
string XPath = "//sql:Batch/sql:Statements/sql:StmtSimple/sql:QueryPlan/sql:RelOp/@PhysicalOp";
string XPath_2 = "//sql:Batch/sql:Statements/sql:StmtSimple/sql:QueryPlan/sql:RelOp//sql:StreamAggregate/sql:RelOp/sql:NestedLoops/sql:RelOp[1]/sql:Sort/sql:RelOp/sql:NestedLoops/sql:RelOp/sql:NestedLoops/sql:RelOp/sql:NestedLoops/sql:RelOp[1]/@PhysicalOp";
string XPath_3 = "//sql:Batch/sql:Statements/sql:StmtSimple/sql:QueryPlan/sql:RelOp//sql:StreamAggregate/sql:RelOp/sql:NestedLoops/sql:RelOp[1]/sql:Sort/sql:RelOp/sql:NestedLoops/sql:RelOp/sql:NestedLoops/sql:RelOp/sql:NestedLoops/sql:RelOp[2]/@PhysicalOp";
string XPath_4 = "//sql:Batch/sql:Statements/sql:StmtSimple/sql:QueryPlan/sql:RelOp//sql:StreamAggregate/sql:RelOp/sql:NestedLoops/sql:RelOp[1]/sql:Sort/sql:RelOp/sql:NestedLoops/sql:RelOp/sql:NestedLoops/sql:RelOp/sql:NestedLoops/sql:RelOp/@EstimatedTotalSubtreeCost";
for (int i = 0; i < batches.Length; i++)
{
string _conn = constring_1;
string batch = batches[i];
SqlDataAdapter da = new SqlDataAdapter(batch, _conn);
DataSet ds = new DataSet();
try
{
da.Fill(ds);
}
catch (Exception e)
{
string Error = e.Message;
}
da.Dispose();
DataTable dt = ds.Tables["Table"];
if (i == 2)
{
string QueryPlan = qp.GetQueryPlanForQuery(constring_1, batch);
string _operator = qv.GetSingleValue(QueryPlan, SqlNamespace, XPath);
Assert.AreEqual("Stream Aggregate", _operator);
string _operator_2 = qv.GetSingleValue(QueryPlan, SqlNamespace, XPath_2);
Assert.AreEqual("Index Seek", _operator_2);
string _operator_3 = qv.GetSingleValue(QueryPlan, SqlNamespace, XPath_3);
Assert.AreEqual("Index Seek", _operator_3);
string _operator_4 = qv.GetSumOfValues(QueryPlan, SqlNamespace, XPath_4);
Assert.IsTrue(0.01 > Convert.ToSingle(_operator_4));
}
}
}
}
}
If you have any questions/ideas/improvements then leave a comment below, or DM me on Twitter, or take a clone of the repo, make the changes and let me know!