Creating an HTML report of schema differences in Visual Basic .NET
Published 01 March 2013
SQL Compare's API does not directly support the creation of HTML reports as the user interface of the program does. By manually generating an XML document, a report can be generated by transforming the XML using the XSL template supplied with the SQL Compare program. This report code examines the differences between databases WidgetStaging and WidgetProduction, generates suitable XML, and converts it to HTML using the template SQLCompareInteractiveReportTemplate.xsl.
'==================================================================== ' Save the following as Module1.vb '==================================================================== Imports RedGate.SQLCompare.Engine Imports RedGate.Shared.SQL Imports System.Diagnostics Module Module1 Sub Main() Using dbSource As New Database(), dbTarget As New Database() 'Retrieve the schema information for the two databases Console.WriteLine("Registering databases") dbSource.Register(New ConnectionProperties(".", "WidgetStaging"), Options.Default) dbTarget.Register(New ConnectionProperties(".", "WidgetProduction"), Options.Default) Console.WriteLine("Comparing Databases") Dim dbSourceVsdbTarget As Differences = dbSource.CompareWith(dbTarget, Options.Default) ' Set the filespec for our HTML report Dim ReportOutput As String = "c:\Program files\Red Gate\SQL Compare 7\htmlreport.html" ' Set the XSL template to use for the report. These ship with the SQL Compare software Dim xsltemplate As String = "c:\Program files\Red Gate\SQL Compare 7\SQLCompareInteractiveReportTemplate.xsl" Console.WriteLine("Creating report...") HTMLReport.CreateHtmlReport(ReportOutput, dbSource, dbTarget, dbSourceVsdbTarget, Options.Default, xsltemplate) Console.WriteLine("Finished creating {0}, viewing", ReportOutput) HTMLReport.ViewReport(ReportOutput) End Using Console.WriteLine("Press any key to continue") Console.ReadLine() End Sub End Module '==================================================================== ' EOF Module1.vb '==================================================================== '==================================================================== ' Save the following as HTMLReport.vb '==================================================================== Imports System Imports System.Collections Imports System.Data Imports System.IO Imports System.Xml Imports System.Xml.Xsl Imports System.Text Imports System.Reflection Imports RedGate.SQLCompare.Engine Imports RedGate.Shared.SQL Imports System.Diagnostics Imports RedGate.Shared.Utils Imports System.Collections.Generic Public Class HTMLReport ' This method will create the XML needed for the report and transform it to an HTML page ' specified by fileName. It looks in the current folder for the template file. ' Please supply the two database objects, the Differences object that you get after a comparison, ' and the set of options that you used for the comparison. Public Shared Sub CreateHtmlReport(ByVal fileName As String, ByVal dbSourceDatabase As Database,ByVal dbTargetDatabase As Database, ByVal obDatabaseDifferences As Differences, ByVal enOptions AsOptions, ByVal xlstemplate As String) Dim tempFile As String = Path.GetTempFileName() Dim xslt As XslCompiledTransform = New XslCompiledTransform() 'Load the XSL template Dim xSettings As XsltSettings = New XsltSettings() xSettings.EnableScript = True xslt.Load(xlstemplate, xSettings, New XmlUrlResolver()) Try Dim writer As XmlTextWriter = New XmlTextWriter(tempFile, Encoding.Unicode) ' Generate the raw data that will go into the report GenerateXml(writer, dbSourceDatabase, dbTargetDatabase, obDatabaseDifferences, enOptions) writer.Close() xslt.Transform(tempFile, fileName) Catch e As Exception Console.WriteLine("Unable to generate html report " + e.Message) Finally File.Delete(tempFile) End Try End Sub ''' <summary> ''' This is the method that creates the XML data used in the report (SQL Compare v7) ''' </summary> ''' <param name="writer">XmlTextWriter object</param> ''' <param name="dbSourceDatabase">A registered database</param> ''' <param name="dbTargetDatabase">The second registered database</param> ''' <param name="obDatabaseDifferences">The differences between the two databases</param> ''' <param name="m_Options">Set of options used during the comparison process</param> Private Shared Sub GenerateXml(ByVal writer As XmlTextWriter, ByVal dbSourceDatabase As Database,ByVal dbTargetDatabase As Database, ByVal obDatabaseDifferences As Differences, ByVal options As Options) writer.WriteStartDocument() ' Header writer.WriteStartElement("comparison") writer.WriteAttributeString("direction", "1to2") writer.WriteAttributeString("timestamp", DateTime.Now.ToString()) 'Datasources writer.WriteStartElement("datasources") writer.WriteStartElement("datasource") writer.WriteAttributeString("type", "live") writer.WriteAttributeString("id", "1") writer.WriteStartElement("server") writer.WriteString(dbSourceDatabase.ConnectionProperties.ServerName) writer.WriteEndElement() ' </server> writer.WriteStartElement("database") writer.WriteString(dbSourceDatabase.ConnectionProperties.DatabaseName) writer.WriteEndElement() ' </database> writer.WriteEndElement() ' <datasource[@id=1]> 'Second database writer.WriteStartElement("datasource") writer.WriteAttributeString("type", "live") writer.WriteAttributeString("id", "2") writer.WriteStartElement("server") writer.WriteString(dbTargetDatabase.ConnectionProperties.ServerName) writer.WriteEndElement() ' </server> writer.WriteStartElement("database") writer.WriteString(dbTargetDatabase.ConnectionProperties.DatabaseName) writer.WriteEndElement() ' </database> writer.WriteEndElement() ' </datasource> writer.WriteEndElement() ' </datasources> 'Differences collection writer.WriteStartElement("differences") Dim d As Difference For Each d In obDatabaseDifferences If d.Type = DifferenceType.Equal Then Continue For If d.Selected = False Then Continue For ' do not report equal and nonselected objects writer.WriteStartElement("difference") writer.WriteAttributeString("objecttype", d.DatabaseObjectType.ToString().ToLower()) writer.WriteAttributeString("status", d.Type.ToString().Trim().ToLower()) writer.WriteAttributeString("fqn", String.Format("{0}-{1}", d.DatabaseObjectType.ToString().ToLower(), d.Name.ToString().ToLower())) Select Case d.Type Case DifferenceType.OnlyIn1 writer.WriteStartElement("object") writer.WriteAttributeString("owner", d.ObjectIn1.Owner) writer.WriteAttributeString("id", "1") writer.WriteString(d.ObjectIn1.FullyQualifiedName) writer.WriteEndElement() writer.WriteStartElement("object") writer.WriteAttributeString("owner", "") writer.WriteAttributeString("id", "2") writer.WriteEndElement() Case DifferenceType.OnlyIn2 writer.WriteStartElement("object") writer.WriteAttributeString("owner", "") writer.WriteAttributeString("id", "1") writer.WriteEndElement() writer.WriteStartElement("object") writer.WriteAttributeString("owner", d.ObjectIn2.Owner) writer.WriteAttributeString("id", "2") writer.WriteString(d.ObjectIn2.FullyQualifiedName) writer.WriteEndElement() Case Else ' object exists in both writer.WriteStartElement("object") writer.WriteAttributeString("owner", d.ObjectIn1.Owner) writer.WriteAttributeString("id", "1") writer.WriteString(d.ObjectIn1.FullyQualifiedName) writer.WriteEndElement() writer.WriteStartElement("object") writer.WriteAttributeString("owner", d.ObjectIn2.Owner) writer.WriteAttributeString("id", "2") writer.WriteString(d.ObjectIn2.FullyQualifiedName) writer.WriteEndElement() End Select ' Now we write the actual SQL code for the objects in database 1 and 2 ' Since the reordering of lines is copyright code in SQL Compare ' we are going to simply dump the SQL in the order it comes writer.WriteStartElement("comparisonstrings") Dim w As Work = New Work() Dim regions1 As Regions = w.ScriptObject(d.ObjectIn1, options) Dim regions2 As Regions = w.ScriptObject(d.ObjectIn2, options) ' Work out which region is "shortest" Dim regionCount As Integer = regions1.Count Dim oneIsLonger As Boolean = True If regions2.Count > regions1.Count Then regionCount = regions2.Count oneIsLonger = False End If 'loop through all SQL regions -- append the longer lines Dim j As Integer = 0 For j = 0 To regionCount - 1 'Start writing out the lines of SQL code Dim oneHasMoreLines As Boolean = False Dim linesFrom1() As String Dim linesFrom2() As String Try linesFrom1 = regions1(j).SQL.Split(vbCrLf) Catch a As ArgumentOutOfRangeException ' There are more regions in region2 ReDim Preserve linesFrom1(regions2(j).SQL.Split(vbCrLf).Length) Dim y As Integer = 0 For y = 0 To linesFrom1.Length - 1 linesFrom1(y) = String.Empty Next y End Try Try linesFrom2 = regions2(j).SQL.Split(vbCrLf) Catch a As ArgumentOutOfRangeException ' There are more regions in region1 ReDim Preserve linesFrom2(regions1(j).SQL.Split(vbCrLf).Length) Dim y As Integer = 0 For y = 0 To linesFrom2.Length - 1 linesFrom2(y) = String.Empty Next y End Try Dim sqlLineCount As Integer = linesFrom1.Length Dim sqlLineCount2 As Integer = linesFrom2.Length If sqlLineCount > sqlLineCount2 Then sqlLineCount = sqlLineCount2 oneHasMoreLines = True End If Dim l As Integer = 0 For l = 0 To sqlLineCount - 1 writer.WriteStartElement("line") writer.WriteAttributeString("type", IIf(Not String.Compare(linesFrom1(l), linesFrom2(l), True) = 0, "different", "same")) ' Dump the line of SQL from db1 writer.WriteStartElement("left") writer.WriteString(linesFrom1(l).Trim()) writer.WriteEndElement() ' </left> ' ...and db2 writer.WriteStartElement("right") writer.WriteString(linesFrom2(l).Trim()) writer.WriteEndElement() ' </right> writer.WriteEndElement() '</line> Next l 'Write out any "leftover" SQL Dim leftoverSql() As String = linesFrom2 If oneHasMoreLines = True Then leftoverSql = linesFrom1 Dim m As Integer = 0 For m = l To leftoverSql.Length - 1 writer.WriteStartElement("line") writer.WriteAttributeString("type", "different") writer.WriteStartElement("left") If oneHasMoreLines = True Then writer.WriteString(leftoverSql(m).Trim()) writer.WriteEndElement() ' </left> writer.WriteStartElement("right") If oneHasMoreLines = False Then writer.WriteString(leftoverSql(m).Trim()) writer.WriteEndElement() ' </right> writer.WriteEndElement() '</line> Next m Next j writer.WriteEndElement() ' </comparisonStrings> writer.WriteEndElement() ' </difference> Next writer.WriteEndElement() ' </differences> writer.WriteEndElement() ' </comparison> writer.WriteEndDocument() 'EOF End Sub ' Feed the .htm file to Windows and let it start the viewer (IE) Public Shared Sub ViewReport(ByVal sPath As String) If sPath = String.Empty Then Return 'view the doc Try Dim psi As ProcessStartInfo = New ProcessStartInfo(sPath) psi.UseShellExecute = True Process.Start(psi) Catch End Try End Sub End Class '==================================================================== ' EOF HTMLReport.vb '====================================================================