using System; using System.Collections.Generic; using System.Windows.Forms; using Excel = Microsoft.Office.Interop.Excel; using Microsoft.AnalysisServices; using System.Xml; namespace BismNormalizer.TabularCompare.Core { /// /// Represents a comparison of two SSAS tabular models. This class is extended by BismNormalizer.TabularCompare.MultidimensionalMetadata.Comparison and BismNormalizer.TabularCompare.TabularMetadata.Comparison depending on SSAS compatibility level. /// public abstract class Comparison : IDisposable { #region Protetced/Private Members protected List _comparisonObjects; protected ComparisonInfo _comparisonInfo; protected int _comparisonObjectCount = 0; private int _compatibilityLevel; #endregion #region Properties /// /// Collection of ComparisonObject instances. /// public List ComparisonObjects { get { return _comparisonObjects; } set { _comparisonObjects = value; } } /// /// Compatibility level of the SSAS tabular models for this comparison. /// public int CompatibilityLevel => _compatibilityLevel; #endregion #region Events /// /// Occurs when a validation message is surfaced, either warning or informational. /// public event EventHandler ValidationMessage; /// /// Occurs when all messages for a validation are done, and need to dynamically resize the headers. /// public event EventHandler ResizeValidationHeaders; /// /// Invokes the ValidationMessage event. /// /// ValidationMessageEventArgs object. public virtual void OnValidationMessage(ValidationMessageEventArgs e) => ValidationMessage?.Invoke(this, e); /// /// Invokes the ResizeValidationHeaders event. /// /// EventArgs object. public virtual void OnResizeValidationHeaders(EventArgs e) => ResizeValidationHeaders?.Invoke(this, e); /// /// Occurs during database deployment when a password is required for an impersonated account. /// public event EventHandler PasswordPrompt; /// /// Invokes the PasswordPrompt event. /// /// ValidationMessageEventArgs object. public virtual void OnPasswordPrompt(PasswordPromptEventArgs e) => PasswordPrompt?.Invoke(this, e); /// /// Occurs during database deployment when a password is required for a blob key. /// public event EventHandler BlobKeyPrompt; /// /// Invokes the BlobKeyPrompt event. /// /// ValidationMessageEventArgs object. public virtual void OnBlobKeyPrompt(BlobKeyEventArgs e) => BlobKeyPrompt?.Invoke(this, e); /// /// Occurs when a database is ready for deployment. /// public event EventHandler DatabaseDeployment; /// /// Invokes the DatabaseDeployment event. /// /// DatabaseDeploymentEventArgs object. public virtual void OnDatabaseDeployment(DatabaseDeploymentEventArgs e) => DatabaseDeployment?.Invoke(this, e); /// /// Occurs when a deployment status message is surfaced. /// public event EventHandler DeploymentMessage; /// /// Invokes the DeploymentMessage event. /// /// DeploymentMessageEventArgs object. public virtual void OnDeploymentMessage(DeploymentMessageEventArgs e) => DeploymentMessage?.Invoke(this, e); /// /// Occurs when a database deployment is complete. /// public event EventHandler DeploymentComplete; /// /// Invokes the DeploymentComplete event. /// /// DeploymentCompleteEventArgs object. public virtual void OnDeploymentComplete(DeploymentCompleteEventArgs e) => DeploymentComplete?.Invoke(this, e); #endregion #region Constructors /// /// Initializes a new instance of the Comparison class using a ComparisonInfo object. /// /// ComparisonInfo object typically deserialized from a BSMN file. public Comparison(ComparisonInfo comparisonInfo) { _comparisonObjects = new List(); _comparisonInfo = comparisonInfo; //Supported compatibility level - with matching source/target compatibility levels - has already been validated at this point, so can safely use SourceCompatibilityLevel _compatibilityLevel = comparisonInfo.SourceCompatibilityLevel; } #endregion #region Abstract Methods /// /// Connect to source and target tabular models, and instantiate their properties. /// public abstract void Connect(); /// /// Disconnect from source and target tabular models. /// public abstract void Disconnect(); /// /// Validate selection of actions to perform on target tabular model. Warnings and informational messages are provided by invoking ShowStatusMessageCallBack. /// public abstract void ValidateSelection(); /// /// Update target tabular model with changes defined by actions in ComparisonObject instances. /// /// Flag to indicate whether update was successful. public abstract bool Update(); /// /// Gets a collection of ProcessingTable objects depending on Process Affected Tables option. /// /// Collection of ProcessingTable objects. public abstract ProcessingTableCollection GetTablesToProcess(); /// /// Deploy database to target server and perform processing if required. /// /// public abstract void DatabaseDeployAndProcess(ProcessingTableCollection tablesToProcess); /// /// Stop processing of deployed database. /// public abstract void StopProcessing(); /// /// Generate script of target database including changes. /// /// Script. public abstract string ScriptDatabase(); /// /// Compare source and target tabular models. /// public abstract void CompareTabularModels(); #endregion /// /// Finds ComparisonObject matching search criteria. /// /// /// /// /// /// /// ComparisonObject matching search criteria. If none found, null is returned. public ComparisonObject FindComparisonObjectByObjectInternalNames(string sourceObjectName, string sourceObjectId, string targetObjectName, string targetObjectId, ComparisonObjectType objType) { foreach (ComparisonObject comparisonObject in _comparisonObjects) { ComparisonObject matchedComparisonObj; if (CheckComparisonObject(comparisonObject, sourceObjectName, sourceObjectId, targetObjectName, targetObjectId, objType, out matchedComparisonObj)) { return matchedComparisonObj; } } // if didn't find a match, return null return null; } private bool CheckComparisonObject(ComparisonObject comparisonObject, string sourceObjectName, string sourceObjectId, string targetObjectName, string targetObjectId, ComparisonObjectType objType, out ComparisonObject matchedComparisonObj) { if (comparisonObject.SourceObjectName == sourceObjectName && comparisonObject.SourceObjectInternalName == sourceObjectId && comparisonObject.TargetObjectName == targetObjectName && comparisonObject.TargetObjectInternalName == targetObjectId && comparisonObject.ComparisonObjectType == objType) { matchedComparisonObj = comparisonObject; return true; } foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects) { if (CheckComparisonObject(childComparisonObject, sourceObjectName, sourceObjectId, targetObjectName, targetObjectId, objType, out matchedComparisonObj)) { if (matchedComparisonObj == null) matchedComparisonObj = childComparisonObject; return true; } } // if didn't find a match, return null matchedComparisonObj = null; return false; } /// /// Generate Excel report of differences. /// /// public void ReportDifferences(ToolStripProgressBar progBar) { try { progBar.Maximum = _comparisonObjectCount; progBar.Value = 0; Excel.Application App = new Excel.Application(); Excel.Workbook Wb = App.Workbooks.Add(); //Wb.Sheets[2].Delete(); //Wb.Sheets[1].Delete(); Excel.Worksheet Ws = default(Excel.Worksheet); Ws = Wb.ActiveSheet; Ws.Name = "Comparison Report"; int row = 1, lastDataSourceRow = -1, lastTableRow = -1; // set up headers Ws.Cells[row, 1].Value = "Type"; Ws.Columns[1].ColumnWidth = 20; Ws.Cells[row, 2].Value = "Source Object Name"; Ws.Columns[2].ColumnWidth = 41; Ws.Cells[row, 3].Value = "Source Object Definition"; Ws.Columns[3].ColumnWidth = 24; Ws.Cells[row, 4].Value = "Status"; Ws.Columns[4].ColumnWidth = 18; Ws.Cells[row, 5].Value = "Target Object Name"; Ws.Columns[5].ColumnWidth = 41; Ws.Cells[row, 6].Value = "Target Object Definition"; Ws.Columns[6].ColumnWidth = 24; Ws.Range["A1:F1"].Select(); Ws.Application.Selection.Font.Bold = true; //set up grouping Ws.Outline.AutomaticStyles = false; Ws.Outline.SummaryRow = (Excel.XlSummaryRow)Excel.Constants.xlAbove; Ws.Outline.SummaryColumn = (Excel.XlSummaryColumn)Excel.Constants.xlLeft; foreach (ComparisonObject comparisonObject in _comparisonObjects) { PopulateExcelRow(Ws, ref row, ref lastDataSourceRow, ref lastTableRow, comparisonObject, progBar); } // do we need to close the last groups? if (lastTableRow < row && lastTableRow != -1) { Ws.Application.Rows[Convert.ToString(lastTableRow + 1) + ":" + Convert.ToString(row)].Select(); Ws.Application.Selection.Rows.Group(); } if (lastDataSourceRow < row && lastDataSourceRow != -1) { Ws.Application.Rows[Convert.ToString(lastDataSourceRow + 1) + ":" + Convert.ToString(row)].Select(); Ws.Application.Selection.Rows.Group(); } Ws.Cells.Select(); Ws.Application.Selection.WrapText = false; Ws.Cells[1, 1].Select(); App.Visible = true; progBar.Value = 0; } catch (System.Runtime.InteropServices.COMException exc) { throw new System.Runtime.InteropServices.COMException("Unable to create Excel report. Please check Excel is installed.", exc); } } /// /// Refresh SkipSelections property. /// public void RefreshSkipSelectionsFromComparisonObjects() { _comparisonInfo.SkipSelections.Clear(); foreach (ComparisonObject comparisonObject in this.ComparisonObjects) { RefreshSkipSelectionsFromChildComparisonObjects(comparisonObject); } } private void RefreshSkipSelectionsFromChildComparisonObjects(ComparisonObject comparisonObject) { if (comparisonObject.Status != ComparisonObjectStatus.SameDefinition && comparisonObject.MergeAction == MergeAction.Skip && !_comparisonInfo.SkipSelections.Contains(comparisonObject)) { _comparisonInfo.SkipSelections.Add(new SkipSelection(comparisonObject)); } foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects) { RefreshSkipSelectionsFromChildComparisonObjects(childComparisonObject); } } /// /// Refresh ComparisonObjects property. /// public void RefreshComparisonObjectsFromSkipSelections() { foreach (ComparisonObject comparisonObject in this.ComparisonObjects) { RefreshChildComparisonObjectsFromSkipSelections(comparisonObject); } } private void RefreshChildComparisonObjectsFromSkipSelections(ComparisonObject comparisonObject) { if (comparisonObject.Status != ComparisonObjectStatus.SameDefinition) { foreach (SkipSelection skipSelection in _comparisonInfo.SkipSelections) { if (comparisonObject.Status == skipSelection.Status && comparisonObject.ComparisonObjectType == skipSelection.ComparisonObjectType && (skipSelection.Status == ComparisonObjectStatus.MissingInSource || comparisonObject.SourceObjectInternalName == skipSelection.SourceObjectInternalName) && (skipSelection.Status == ComparisonObjectStatus.MissingInTarget || comparisonObject.TargetObjectInternalName == skipSelection.TargetObjectInternalName)) { comparisonObject.MergeAction = MergeAction.Skip; break; } } } foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects) { RefreshChildComparisonObjectsFromSkipSelections(childComparisonObject); } } private void PopulateExcelRow(Excel.Worksheet Ws, ref int row, ref int lastDataSourceRow, ref int lastTableRow, ComparisonObject comparisonObject, ToolStripProgressBar progBar) { progBar.PerformStep(); row += 1; // Close out groups if necessary if (comparisonObject.ComparisonObjectType == ComparisonObjectType.DataSource || comparisonObject.ComparisonObjectType == ComparisonObjectType.Table || comparisonObject.ComparisonObjectType == ComparisonObjectType.Perspective || comparisonObject.ComparisonObjectType == ComparisonObjectType.Culture || comparisonObject.ComparisonObjectType == ComparisonObjectType.Role || comparisonObject.ComparisonObjectType == ComparisonObjectType.Expression || comparisonObject.ComparisonObjectType == ComparisonObjectType.Action) //treat perspectives/cultures/roles/expressions like datasources for purpose of grouping { // do we need to close a table group? if (lastTableRow + 1 < row && lastTableRow != -1) { Ws.Application.Rows[Convert.ToString(lastTableRow + 1) + ":" + Convert.ToString(row - 1)].Select(); Ws.Application.Selection.Rows.Group(); } lastTableRow = row; } //Type column switch (comparisonObject.ComparisonObjectType) { case ComparisonObjectType.Model: Ws.Cells[row, 1].Value = "Model"; break; case ComparisonObjectType.DataSource: Ws.Cells[row, 1].Value = "Data Source"; break; case ComparisonObjectType.Table: Ws.Cells[row, 1].Value = "Table"; break; case ComparisonObjectType.Relationship: Ws.Cells[row, 1].Value = "Relationship"; Ws.Cells[row, 1].InsertIndent(3); Ws.Cells[row, 2].InsertIndent(3); Ws.Cells[row, 5].InsertIndent(3); break; case ComparisonObjectType.Measure: Ws.Cells[row, 1].Value = "Measure"; Ws.Cells[row, 1].InsertIndent(3); Ws.Cells[row, 2].InsertIndent(3); Ws.Cells[row, 5].InsertIndent(3); break; case ComparisonObjectType.Kpi: Ws.Cells[row, 1].Value = "KPI"; Ws.Cells[row, 1].InsertIndent(3); Ws.Cells[row, 2].InsertIndent(3); Ws.Cells[row, 5].InsertIndent(3); break; case ComparisonObjectType.CalculationItem: Ws.Cells[row, 1].Value = "Calculation Item"; Ws.Cells[row, 1].InsertIndent(3); Ws.Cells[row, 2].InsertIndent(3); Ws.Cells[row, 5].InsertIndent(3); break; case ComparisonObjectType.Perspective: Ws.Cells[row, 1].Value = "Perspective"; break; case ComparisonObjectType.Culture: Ws.Cells[row, 1].Value = "Culture"; break; case ComparisonObjectType.Role: Ws.Cells[row, 1].Value = "Role"; break; case ComparisonObjectType.Expression: Ws.Cells[row, 1].Value = "Expression"; break; case ComparisonObjectType.Action: Ws.Cells[row, 1].Value = "Action"; break; default: Ws.Cells[row, 1].Value = comparisonObject.ComparisonObjectType.ToString(); break; } //Source Obj Name column if (comparisonObject.SourceObjectName != null && comparisonObject.SourceObjectName != "") { Ws.Cells[row, 2].Value = comparisonObject.SourceObjectName; if (comparisonObject.SourceObjectDefinition != null && comparisonObject.SourceObjectDefinition != "") { Ws.Cells[row, 3].Value = comparisonObject.SourceObjectDefinition; } } else { Ws.Cells[row, 2].Interior.Pattern = Excel.Constants.xlSolid; Ws.Cells[row, 2].Interior.PatternColorIndex = Excel.Constants.xlAutomatic; Ws.Cells[row, 2].Interior.ThemeColor = Excel.XlThemeColor.xlThemeColorDark1; Ws.Cells[row, 2].Interior.TintAndShade = -0.149998474074526; Ws.Cells[row, 2].Interior.PatternTintAndShade = 0; Ws.Cells[row, 3].Interior.Pattern = Excel.Constants.xlSolid; Ws.Cells[row, 3].Interior.PatternColorIndex = Excel.Constants.xlAutomatic; Ws.Cells[row, 3].Interior.ThemeColor = Excel.XlThemeColor.xlThemeColorDark1; Ws.Cells[row, 3].Interior.TintAndShade = -0.149998474074526; Ws.Cells[row, 3].Interior.PatternTintAndShade = 0; } //status switch (comparisonObject.Status) { case ComparisonObjectStatus.SameDefinition: Ws.Cells[row, 4].Value = "Same Definition"; break; case ComparisonObjectStatus.DifferentDefinitions: Ws.Cells[row, 4].Value = "Different Definitions"; break; case ComparisonObjectStatus.MissingInTarget: Ws.Cells[row, 4].Value = "Missing in Target"; break; case ComparisonObjectStatus.MissingInSource: Ws.Cells[row, 4].Value = "Missing in Source"; break; default: Ws.Cells[row, 4].Value = comparisonObject.Status.ToString(); break; } //Target Obj Name column if (comparisonObject.TargetObjectName != null && comparisonObject.TargetObjectName != "") { Ws.Cells[row, 5].Value = comparisonObject.TargetObjectName; if (comparisonObject.TargetObjectDefinition != null && comparisonObject.TargetObjectDefinition != "") { Ws.Cells[row, 6].Value = comparisonObject.TargetObjectDefinition; } } else { Ws.Cells[row, 5].Interior.Pattern = Excel.Constants.xlSolid; Ws.Cells[row, 5].Interior.PatternColorIndex = Excel.Constants.xlAutomatic; Ws.Cells[row, 5].Interior.ThemeColor = Excel.XlThemeColor.xlThemeColorDark1; Ws.Cells[row, 5].Interior.TintAndShade = -0.149998474074526; Ws.Cells[row, 5].Interior.PatternTintAndShade = 0; Ws.Cells[row, 6].Interior.Pattern = Excel.Constants.xlSolid; Ws.Cells[row, 6].Interior.PatternColorIndex = Excel.Constants.xlAutomatic; Ws.Cells[row, 6].Interior.ThemeColor = Excel.XlThemeColor.xlThemeColorDark1; Ws.Cells[row, 6].Interior.TintAndShade = -0.149998474074526; Ws.Cells[row, 6].Interior.PatternTintAndShade = 0; } // Insert blank in last cell so defintion doesn't overlap Ws.Cells[row, 7].Value = " "; foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects) { PopulateExcelRow(Ws, ref row, ref lastDataSourceRow, ref lastTableRow, childComparisonObject, progBar); } } #region Helper functions to execute XMLA /// /// Finds row count for a table to display after processing. /// /// /// /// /// Row count. public static Int64 FindRowCount(Microsoft.AnalysisServices.Core.Server server, string tableName, string databaseName) { string dax = String.Format("EVALUATE ROW( \"RowCount\", COUNTROWS('{0}'))", tableName); bool foundFault = false; XmlNodeList rows = ExecuteXmlaCommand(server, databaseName, dax, ref foundFault); foreach (XmlNode row in rows) { XmlNode rowCountNode = null; foreach (XmlNode childNode in row.ChildNodes) { //cbw not good: //if (childNode.Name.Contains("RowCount")) //{ rowCountNode = childNode; //} } int result; if (rowCountNode != null && int.TryParse(rowCountNode.InnerText, out result)) { return result; } } return 0; } /// /// Executes an XMLA command on the tabular model for the connection. /// /// /// /// XmlNodeList containing results of the command execution. public static XmlNodeList ExecuteXmlaCommand(Microsoft.AnalysisServices.Core.Server server, string catalog, string commandStatement, ref bool foundFault) { XmlWriter xmlWriter = server.StartXmlaRequest(XmlaRequestType.Undefined); WriteSoapEnvelopeWithCommandStatement(xmlWriter, server.SessionID, catalog, commandStatement); System.Xml.XmlReader xmlReader = server.EndXmlaRequest(); xmlReader.MoveToContent(); string fullEnvelopeResponseFromServer = xmlReader.ReadOuterXml(); xmlReader.Close(); XmlDocument documentResponse = new XmlDocument(); documentResponse.LoadXml(fullEnvelopeResponseFromServer); XmlNamespaceManager nsmgr = new XmlNamespaceManager(documentResponse.NameTable); nsmgr.AddNamespace("myns1", "urn:schemas-microsoft-com:xml-analysis"); nsmgr.AddNamespace("myns2", "urn:schemas-microsoft-com:xml-analysis:rowset"); XmlNodeList rows = documentResponse.SelectNodes("//myns1:ExecuteResponse/myns1:return/myns2:root/myns2:row", nsmgr); if (rows.Count == 0 && documentResponse.GetElementsByTagName("faultcode").Count > 0) { foundFault = true; } return rows; } private static void WriteSoapEnvelopeWithCommandStatement(XmlWriter xmlWriter, string sessionId, string catalog, string commandStatement) { #region Examples //EXAMPLE1 // //
// //
// // // // // SystemGetSubdirs 'd:\Program Files\Microsoft SQL Server\MSAS11.MSSQLSERVER\OLAP\Data' // // // // // //
//EXAMPLE2 // //
// //
// // // // // EVALUATE ROW( "Result", COUNTROWS('FactInternetSales')) // // // // // Tabular1200 V2 // Tabular // Data // // // // //
#endregion xmlWriter.WriteStartElement("Envelope", "http://schemas.xmlsoap.org/soap/envelope/"); xmlWriter.WriteStartElement("Header"); if (sessionId != null) { xmlWriter.WriteStartElement("Session", "urn:schemas-microsoft-com:xml-analysis"); xmlWriter.WriteAttributeString("soap", "mustUnderstand", "http://schemas.xmlsoap.org/soap/envelope/", "1"); xmlWriter.WriteAttributeString("SessionId", sessionId); xmlWriter.WriteEndElement(); // } xmlWriter.WriteEndElement(); // xmlWriter.WriteStartElement("Body"); xmlWriter.WriteStartElement("Execute", "urn:schemas-microsoft-com:xml-analysis"); xmlWriter.WriteStartElement("Command"); xmlWriter.WriteElementString("Statement", commandStatement); xmlWriter.WriteEndElement(); // xmlWriter.WriteStartElement("Properties"); if (!String.IsNullOrEmpty(catalog)) { xmlWriter.WriteStartElement("PropertyList"); xmlWriter.WriteElementString("Catalog", catalog); xmlWriter.WriteElementString("Format", "Tabular"); xmlWriter.WriteElementString("Content", "Data"); xmlWriter.WriteEndElement(); // } xmlWriter.WriteEndElement(); // xmlWriter.WriteEndElement(); // xmlWriter.WriteEndElement(); // xmlWriter.WriteEndElement(); // } #endregion public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected abstract void Dispose(bool disposing); } }