using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Globalization; using Microsoft.AnalysisServices.Tabular; using Tom=Microsoft.AnalysisServices.Tabular; namespace BismNormalizer.TabularCompare.TabularMetadata { /// /// Abstraction of a tabular model calculationItem with properties and methods for comparison purposes. /// public class CalculationItem : TabularObject { private Table _parentTable; private Tom.CalculationItem _tomCalculationItem; /// /// Initializes a new instance of the CalculationItem class using multiple parameters. /// /// Table object that the calculationItem belongs to. /// Tabular Object Model CalculationItem object abtstracted by the CalculationItem class. /// Indicates whether the calculationItem is a KPI. public CalculationItem(Table parentTable, Tom.CalculationItem tomCalculationItem) : base(tomCalculationItem, parentTable.ParentTabularModel) { _parentTable = parentTable; _tomCalculationItem = tomCalculationItem; } /// /// Table object that the Relationship oject belongs to. /// public Table ParentTable => _parentTable; /// /// Tabular Object Model CalculationItem object abtstracted by the CalculationItem class. /// public Tom.CalculationItem TomCalculationItem => _tomCalculationItem; /// /// Name of the table that the CalculationItem oject belongs to. /// public string TableName => _tomCalculationItem.CalculationGroup.Table.Name; public override string ToString() => this.GetType().FullName; /// /// Find missing calculation dependencies by inspecting the DAX expression for the calculationItem and iterating columns and other calculationItems in the tabular model for validity of the expression. /// /// List of missing dependencies to be displayed or logged as warnings. public List FindMissingCalculationItemDependencies() { List dependencies = new List(); using (StringReader lines = new StringReader(_tomCalculationItem.Expression)) { string line = string.Empty; while ((line = lines.ReadLine()) != null) { if (line.TrimStart().Length > 1 && line.TrimStart().Substring(0, 2) != "--") //Ignore comments { //Todo2: still need to parse for /* blah */ type comments. Currently can show missing dependency that doesn't apply if within a comment string whatsRemainingOfLine = line; while (whatsRemainingOfLine.Contains('[') && whatsRemainingOfLine.Contains(']')) { int openSquareBracketPosition = whatsRemainingOfLine.IndexOf('[', 0); //someone has ]] instead of ] int closeSquareBracketPosition = whatsRemainingOfLine.Replace("]]", " ").IndexOf(']', openSquareBracketPosition + 1); if (openSquareBracketPosition < closeSquareBracketPosition - 1) { string potentialDependency = whatsRemainingOfLine.Substring(openSquareBracketPosition + 1, closeSquareBracketPosition - openSquareBracketPosition - 1); if (!potentialDependency.Contains('"') && !_tomCalculationItem.Expression.Contains($"\"{potentialDependency}\"") && //it's possible the calculationItem itself is deriving the column name from an ADDCOLUMNS for example !dependencies.Contains(potentialDependency)) { //someone did a replace on ] with ]] dependencies.Add(potentialDependency); } } whatsRemainingOfLine = whatsRemainingOfLine.Substring(closeSquareBracketPosition + 1); } } } } List missingDependencies = new List(); foreach (string dependency in dependencies) { bool foundDependency = false; foreach (Table table in _parentTable.ParentTabularModel.Tables) { //Check if another calculationItem or column has same name if (table.CalculationItems.ContainsNameCaseInsensitive(dependency) || table.ColumnsContainsNameCaseInsensitive(dependency)) { foundDependency = true; break; } } if (!foundDependency) { missingDependencies.Add(dependency); } } return missingDependencies; } } }