Analysis-Services/AlmToolkit/BismNormalizer/TabularCompare/TabularMetadata/Comparison.cs
2023-09-28 08:08:39 -07:00

2010 lines
113 KiB
C#

using System;
using System.Collections.Generic;
using BismNormalizer.TabularCompare.Core;
using Microsoft.AnalysisServices.Tabular;
namespace BismNormalizer.TabularCompare.TabularMetadata
{
/// <summary>
/// Represents a source vs. target comparison of an SSAS tabular model. This class is for tabular models that use tabular metadata with SSAS compatibility level 1200 or above.
/// </summary>
public class Comparison : Core.Comparison
{
#region Private Variables
private TabularModel _sourceTabularModel;
private TabularModel _targetTabularModel;
private bool _uncommitedChanges = false;
private DateTime _lastSourceSchemaUpdate = DateTime.MinValue;
private DateTime _lastTargetSchemaUpdate = DateTime.MinValue;
private bool _disposed = false;
#endregion
#region Properties
/// <summary>
/// TabularModel object being used as the source for comparison.
/// </summary>
public TabularModel SourceTabularModel
{
get { return _sourceTabularModel; }
set { _sourceTabularModel = value; }
}
/// <summary>
/// TabularModel object being used as the target for comparison.
/// </summary>
public TabularModel TargetTabularModel
{
get { return _targetTabularModel; }
set { _targetTabularModel = value; }
}
#endregion
public Comparison(ComparisonInfo comparisonInfo)
: base(comparisonInfo)
{
_sourceTabularModel = new TabularModel(this, comparisonInfo.ConnectionInfoSource, comparisonInfo);
_targetTabularModel = new TabularModel(this, comparisonInfo.ConnectionInfoTarget, comparisonInfo);
}
/// <summary>
/// Connect to source and target tabular models, and instantiate their properties.
/// </summary>
public override void Connect()
{
_sourceTabularModel.Connect();
_targetTabularModel.Connect();
}
/// <summary>
/// Disconnect from source and target tabular models.
/// </summary>
public override void Disconnect()
{
_sourceTabularModel.Disconnect();
_targetTabularModel.Disconnect();
}
public override void CompareTabularModels()
{
_comparisonObjectCount = 0;
#region Model
if (_comparisonInfo.TargetCompatibilityLevel >= 1460) //Target compat level is always >= source one.
{
// check if Model object definition is different
ComparisonObject comparisonObjectModel;
if (_sourceTabularModel.Model.ObjectDefinition != _targetTabularModel.Model.ObjectDefinition)
{
comparisonObjectModel = new ComparisonObject(ComparisonObjectType.Model, ComparisonObjectStatus.DifferentDefinitions, _sourceTabularModel.Model, _targetTabularModel.Model, MergeAction.Update);
_comparisonObjects.Add(comparisonObjectModel);
_comparisonObjectCount += 1;
}
else
{
// they are equal, ...
comparisonObjectModel = new ComparisonObject(ComparisonObjectType.Model, ComparisonObjectStatus.SameDefinition, _sourceTabularModel.Model, _targetTabularModel.Model, MergeAction.Skip);
_comparisonObjects.Add(comparisonObjectModel);
_comparisonObjectCount += 1;
}
}
#endregion
#region DataSources
foreach (DataSource dataSourceSource in _sourceTabularModel.DataSources)
{
// check if source is not in target
if (!_targetTabularModel.DataSources.ContainsName(dataSourceSource.Name))
{
ComparisonObject comparisonObjectDataSource = new ComparisonObject(ComparisonObjectType.DataSource, ComparisonObjectStatus.MissingInTarget, dataSourceSource, null, MergeAction.Create);
_comparisonObjects.Add(comparisonObjectDataSource);
_comparisonObjectCount += 1;
}
else
{
// there is a DataSource in the target with the same name at least
DataSource dataSourceTarget = _targetTabularModel.DataSources.FindByName(dataSourceSource.Name);
ComparisonObject comparisonObjectDataSource;
// check if DataSource object definition is different
if (dataSourceSource.ObjectDefinition != dataSourceTarget.ObjectDefinition)
{
comparisonObjectDataSource = new ComparisonObject(ComparisonObjectType.DataSource, ComparisonObjectStatus.DifferentDefinitions, dataSourceSource, dataSourceTarget, MergeAction.Update);
_comparisonObjects.Add(comparisonObjectDataSource);
_comparisonObjectCount += 1;
}
else
{
// they are equal, ...
comparisonObjectDataSource = new ComparisonObject(ComparisonObjectType.DataSource, ComparisonObjectStatus.SameDefinition, dataSourceSource, dataSourceTarget, MergeAction.Skip);
_comparisonObjects.Add(comparisonObjectDataSource);
_comparisonObjectCount += 1;
}
}
}
foreach (DataSource dataSourceTarget in _targetTabularModel.DataSources)
{
// if target DataSource is Missing in Source, offer deletion
if (!_sourceTabularModel.DataSources.ContainsName(dataSourceTarget.Name))
{
ComparisonObject comparisonObjectDataSource = new ComparisonObject(ComparisonObjectType.DataSource, ComparisonObjectStatus.MissingInSource, null, dataSourceTarget, MergeAction.Delete);
_comparisonObjects.Add(comparisonObjectDataSource);
_comparisonObjectCount += 1;
}
}
#endregion
#region Tables
foreach (Table tblSource in _sourceTabularModel.Tables)
{
// check if source is not in target
TableCollection targetTablesForComparison = _targetTabularModel.Tables;
if (!targetTablesForComparison.ContainsName(tblSource.Name))
{
ComparisonObject comparisonObjectTable = new ComparisonObject(ComparisonObjectType.Table, ComparisonObjectStatus.MissingInTarget, tblSource, null, MergeAction.Create);
_comparisonObjects.Add(comparisonObjectTable);
_comparisonObjectCount += 1;
#region Relationships for table Missing in Target
// all relationships in source are not in target (the target table doesn't even exist)
foreach (Relationship relSource in tblSource.Relationships)
{
ComparisonObject comparisonObjectRelation = new ComparisonObject(ComparisonObjectType.Relationship, ComparisonObjectStatus.MissingInTarget, relSource, null, MergeAction.Create);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectRelation);
_comparisonObjectCount += 1;
}
#endregion
#region Measures / KPIs for Table that is Missing in Target
foreach (Measure measureSource in tblSource.Measures.FilterByTableName(tblSource.Name))
{
ComparisonObjectType comparisonObjectType = measureSource.IsKpi ? ComparisonObjectType.Kpi : ComparisonObjectType.Measure;
ComparisonObject comparisonObjectMeasure = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.MissingInTarget, measureSource, null, MergeAction.Create);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectMeasure);
_comparisonObjectCount += 1;
}
#endregion
#region CalculationItems for Table that is Missing in Target
foreach (CalculationItem calculationItemSource in tblSource.CalculationItems.FilterByTableName(tblSource.Name))
{
ComparisonObjectType comparisonObjectType = ComparisonObjectType.CalculationItem;
ComparisonObject comparisonObjectCalculationItem = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.MissingInTarget, calculationItemSource, null, MergeAction.Create);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectCalculationItem);
_comparisonObjectCount += 1;
}
#endregion
}
else
{
//table name is in source and target
Table tblTarget = _targetTabularModel.Tables.FindByName(tblSource.Name);
ComparisonObject comparisonObjectTable;
if (tblSource.ObjectDefinition == tblTarget.ObjectDefinition)
{
comparisonObjectTable = new ComparisonObject(ComparisonObjectType.Table, ComparisonObjectStatus.SameDefinition, tblSource, tblTarget, MergeAction.Skip);
_comparisonObjects.Add(comparisonObjectTable);
_comparisonObjectCount += 1;
}
else
{
comparisonObjectTable = new ComparisonObject(ComparisonObjectType.Table, ComparisonObjectStatus.DifferentDefinitions, tblSource, tblTarget, MergeAction.Update);
_comparisonObjects.Add(comparisonObjectTable);
_comparisonObjectCount += 1;
}
#region Relationships source/target tables exist
foreach (Relationship relSource in tblSource.Relationships)
{
// check if source is not in target
if (!tblTarget.Relationships.ContainsName(relSource.Name)) //Using Name, not InternalName in case internal name is different
{
ComparisonObject comparisonObjectRelation = new ComparisonObject(ComparisonObjectType.Relationship, ComparisonObjectStatus.MissingInTarget, relSource, null, MergeAction.Create);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectRelation);
_comparisonObjectCount += 1;
}
else
{
//relationship is in source and target
Relationship relTarget = tblTarget.Relationships.FindByName(relSource.Name);
ComparisonObject comparisonObjectRelationship;
if (relSource.ObjectDefinition == relTarget.ObjectDefinition)
{
comparisonObjectRelationship = new ComparisonObject(ComparisonObjectType.Relationship, ComparisonObjectStatus.SameDefinition, relSource, relTarget, MergeAction.Skip);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectRelationship);
_comparisonObjectCount += 1;
}
else
{
comparisonObjectRelationship = new ComparisonObject(ComparisonObjectType.Relationship, ComparisonObjectStatus.DifferentDefinitions, relSource, relTarget, MergeAction.Update);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectRelationship);
_comparisonObjectCount += 1;
}
}
}
// see if relationships in target table that don't exist in source table
foreach (Relationship relTarget in tblTarget.Relationships)
{
// check if source is not in target
if (!tblSource.Relationships.ContainsName(relTarget.Name)) //Using Name, not InternalName in case internal name is different
{
ComparisonObject comparisonObjectRelation = new ComparisonObject(ComparisonObjectType.Relationship, ComparisonObjectStatus.MissingInSource, null, relTarget, MergeAction.Delete);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectRelation);
_comparisonObjectCount += 1;
}
}
#endregion
#region Measures / KPIs (table in source and target)
// see if matching measure in source and target
foreach (Measure measureSource in tblSource.Measures.FilterByTableName(tblSource.Name))
{
ComparisonObjectType comparisonObjectType = measureSource.IsKpi ? ComparisonObjectType.Kpi : ComparisonObjectType.Measure;
if (tblTarget.Measures.FilterByTableName(tblTarget.Name).ContainsName(measureSource.Name))
{
//Measure in source and target, so check definition
Measure measureTarget = tblTarget.Measures.FilterByTableName(tblTarget.Name).FindByName(measureSource.Name);
if (measureSource.ObjectDefinition == measureTarget.ObjectDefinition)
{
//Measure has same definition
ComparisonObject comparisonObjectMeasure = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.SameDefinition, measureSource, measureTarget, MergeAction.Skip);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectMeasure);
_comparisonObjectCount += 1;
}
else
{
//Measure has different definition
ComparisonObject comparisonObjectMeasure = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.DifferentDefinitions, measureSource, measureTarget, MergeAction.Update);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectMeasure);
_comparisonObjectCount += 1;
}
}
else
{
ComparisonObject comparisonObjectMeasure = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.MissingInTarget, measureSource, null, MergeAction.Create);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectMeasure);
_comparisonObjectCount += 1;
}
}
//now check if target contains measures Missing in Source
foreach (Measure measureTarget in tblTarget.Measures.FilterByTableName(tblTarget.Name))
{
ComparisonObjectType comparisonObjectType = measureTarget.IsKpi ? ComparisonObjectType.Kpi : ComparisonObjectType.Measure;
if (!tblSource.Measures.FilterByTableName(tblSource.Name).ContainsName(measureTarget.Name))
{
ComparisonObject comparisonObjectMeasure = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.MissingInSource, null, measureTarget, MergeAction.Delete);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectMeasure);
_comparisonObjectCount += 1;
}
}
#endregion
#region CalculationItems (table in source and target)
// see if matching calculationItem in source and target
foreach (CalculationItem calculationItemSource in tblSource.CalculationItems.FilterByTableName(tblSource.Name))
{
ComparisonObjectType comparisonObjectType = ComparisonObjectType.CalculationItem;
if (tblTarget.CalculationItems.FilterByTableName(tblTarget.Name).ContainsName(calculationItemSource.Name))
{
//CalculationItem in source and target, so check definition
CalculationItem calculationItemTarget = tblTarget.CalculationItems.FilterByTableName(tblTarget.Name).FindByName(calculationItemSource.Name);
if (calculationItemSource.ObjectDefinition == calculationItemTarget.ObjectDefinition)
{
//CalculationItem has same definition
ComparisonObject comparisonObjectCalculationItem = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.SameDefinition, calculationItemSource, calculationItemTarget, MergeAction.Skip);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectCalculationItem);
_comparisonObjectCount += 1;
}
else
{
//CalculationItem has different definition
ComparisonObject comparisonObjectCalculationItem = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.DifferentDefinitions, calculationItemSource, calculationItemTarget, MergeAction.Update);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectCalculationItem);
_comparisonObjectCount += 1;
}
}
else
{
ComparisonObject comparisonObjectCalculationItem = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.MissingInTarget, calculationItemSource, null, MergeAction.Create);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectCalculationItem);
_comparisonObjectCount += 1;
}
}
//now check if target contains calculationItems Missing in Source
foreach (CalculationItem calculationItemTarget in tblTarget.CalculationItems.FilterByTableName(tblTarget.Name))
{
ComparisonObjectType comparisonObjectType = ComparisonObjectType.CalculationItem;
if (!tblSource.CalculationItems.FilterByTableName(tblSource.Name).ContainsName(calculationItemTarget.Name))
{
ComparisonObject comparisonObjectCalculationItem = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.MissingInSource, null, calculationItemTarget, MergeAction.Delete);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectCalculationItem);
_comparisonObjectCount += 1;
}
}
#endregion
}
}
foreach (Table tblTarget in _targetTabularModel.Tables)
{
// check if target is not in source
if (!_sourceTabularModel.Tables.ContainsName(tblTarget.Name))
{
ComparisonObject comparisonObjectTable = new ComparisonObject(ComparisonObjectType.Table, ComparisonObjectStatus.MissingInSource, null, tblTarget, MergeAction.Delete);
_comparisonObjects.Add(comparisonObjectTable);
_comparisonObjectCount += 1;
#region Relationships for table Missing in Source
// all relationships in target are not in source (the source table doesn't even exist)
foreach (Relationship relTarget in tblTarget.Relationships)
{
ComparisonObject comparisonObjectRelation = new ComparisonObject(ComparisonObjectType.Relationship, ComparisonObjectStatus.MissingInSource, null, relTarget, MergeAction.Delete);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectRelation);
_comparisonObjectCount += 1;
}
#endregion
#region Measures for Table that is Missing in Source
foreach (Measure measureTarget in tblTarget.Measures.FilterByTableName(tblTarget.Name))
{
ComparisonObjectType comparisonObjectType = measureTarget.IsKpi ? ComparisonObjectType.Kpi : ComparisonObjectType.Measure;
ComparisonObject comparisonObjectMeasure = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.MissingInSource, null, measureTarget, MergeAction.Delete);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectMeasure);
_comparisonObjectCount += 1;
}
#endregion
#region CalculationItems for Table that is Missing in Source
foreach (CalculationItem calculationItemTarget in tblTarget.CalculationItems.FilterByTableName(tblTarget.Name))
{
ComparisonObjectType comparisonObjectType = ComparisonObjectType.CalculationItem;
ComparisonObject comparisonObjectCalculationItem = new ComparisonObject(comparisonObjectType, ComparisonObjectStatus.MissingInSource, null, calculationItemTarget, MergeAction.Delete);
comparisonObjectTable.ChildComparisonObjects.Add(comparisonObjectCalculationItem);
_comparisonObjectCount += 1;
}
#endregion
}
}
#endregion
#region Expressions
foreach (Expression expressionSource in _sourceTabularModel.Expressions)
{
// check if source is not in target
if (!_targetTabularModel.Expressions.ContainsName(expressionSource.Name))
{
ComparisonObject comparisonObjectExpression = new ComparisonObject(ComparisonObjectType.Expression, ComparisonObjectStatus.MissingInTarget, expressionSource, null, MergeAction.Create);
_comparisonObjects.Add(comparisonObjectExpression);
_comparisonObjectCount += 1;
}
else
{
// there is a expression in the target with the same name at least
Expression expressionTarget = _targetTabularModel.Expressions.FindByName(expressionSource.Name);
ComparisonObject comparisonObjectExpression;
// check if expression object definition is different
if (expressionSource.ObjectDefinition != expressionTarget.ObjectDefinition)
{
comparisonObjectExpression = new ComparisonObject(ComparisonObjectType.Expression, ComparisonObjectStatus.DifferentDefinitions, expressionSource, expressionTarget, MergeAction.Update);
_comparisonObjects.Add(comparisonObjectExpression);
_comparisonObjectCount += 1;
}
else
{
// they are equal, ...
comparisonObjectExpression = new ComparisonObject(ComparisonObjectType.Expression, ComparisonObjectStatus.SameDefinition, expressionSource, expressionTarget, MergeAction.Skip);
_comparisonObjects.Add(comparisonObjectExpression);
_comparisonObjectCount += 1;
}
}
}
foreach (Expression expressionTarget in _targetTabularModel.Expressions)
{
// if target expression is Missing in Source, offer deletion
if (!_sourceTabularModel.Expressions.ContainsName(expressionTarget.Name))
{
ComparisonObject comparisonObjectExpression = new ComparisonObject(ComparisonObjectType.Expression, ComparisonObjectStatus.MissingInSource, null, expressionTarget, MergeAction.Delete);
_comparisonObjects.Add(comparisonObjectExpression);
_comparisonObjectCount += 1;
}
}
#endregion
#region Perspectives
if (_comparisonInfo.OptionsInfo.OptionPerspectives)
{
foreach (Perspective perspectiveSource in _sourceTabularModel.Perspectives)
{
// check if source is not in target
if (!_targetTabularModel.Perspectives.ContainsName(perspectiveSource.Name))
{
ComparisonObject comparisonObjectPerspective = new ComparisonObject(ComparisonObjectType.Perspective, ComparisonObjectStatus.MissingInTarget, perspectiveSource, null, MergeAction.Create);
_comparisonObjects.Add(comparisonObjectPerspective);
_comparisonObjectCount += 1;
}
else
{
// there is a perspective in the target with the same name at least
Perspective perspectiveTarget = _targetTabularModel.Perspectives.FindByName(perspectiveSource.Name);
ComparisonObject comparisonObjectPerspective;
// check if perspective object definition is different
//if (perspectiveSource.ObjectDefinition != perspectiveTarget.ObjectDefinition)
if ( (_comparisonInfo.OptionsInfo.OptionMergePerspectives && perspectiveTarget.ContainsOtherPerspectiveSelections(perspectiveSource)) ||
(!_comparisonInfo.OptionsInfo.OptionMergePerspectives && perspectiveTarget.ContainsOtherPerspectiveSelections(perspectiveSource) && perspectiveSource.ContainsOtherPerspectiveSelections(perspectiveTarget)) )
{
// they are equal, ...
comparisonObjectPerspective = new ComparisonObject(ComparisonObjectType.Perspective, ComparisonObjectStatus.SameDefinition, perspectiveSource, perspectiveTarget, MergeAction.Skip);
_comparisonObjects.Add(comparisonObjectPerspective);
_comparisonObjectCount += 1;
}
else
{
comparisonObjectPerspective = new ComparisonObject(ComparisonObjectType.Perspective, ComparisonObjectStatus.DifferentDefinitions, perspectiveSource, perspectiveTarget, MergeAction.Update);
_comparisonObjects.Add(comparisonObjectPerspective);
_comparisonObjectCount += 1;
}
}
}
foreach (Perspective perspectiveTarget in _targetTabularModel.Perspectives)
{
// if target perspective is Missing in Source, offer deletion
if (!_sourceTabularModel.Perspectives.ContainsName(perspectiveTarget.Name))
{
ComparisonObject comparisonObjectPerspective = new ComparisonObject(ComparisonObjectType.Perspective, ComparisonObjectStatus.MissingInSource, null, perspectiveTarget, MergeAction.Delete);
_comparisonObjects.Add(comparisonObjectPerspective);
_comparisonObjectCount += 1;
}
}
}
#endregion
#region Cultures
if (_comparisonInfo.OptionsInfo.OptionCultures)
{
foreach (Culture cultureSource in _sourceTabularModel.Cultures)
{
// check if source is not in target
if (!_targetTabularModel.Cultures.ContainsName(cultureSource.Name))
{
ComparisonObject comparisonObjectCulture = new ComparisonObject(ComparisonObjectType.Culture, ComparisonObjectStatus.MissingInTarget, cultureSource, null, MergeAction.Create);
_comparisonObjects.Add(comparisonObjectCulture);
_comparisonObjectCount += 1;
}
else
{
// there is a culture in the target with the same name at least
Culture cultureTarget = _targetTabularModel.Cultures.FindByName(cultureSource.Name);
ComparisonObject comparisonObjectCulture;
string sourceLinguisticMetadata = String.Empty;
string targetLinguisticMetadata = String.Empty;
if (cultureSource.TomCulture?.LinguisticMetadata?.Content != null)
sourceLinguisticMetadata = Newtonsoft.Json.Linq.JToken.Parse(cultureSource.TomCulture.LinguisticMetadata.Content).ToString();
if (cultureTarget.TomCulture?.LinguisticMetadata?.Content != null)
targetLinguisticMetadata = Newtonsoft.Json.Linq.JToken.Parse(cultureTarget.TomCulture.LinguisticMetadata.Content).ToString();
// check if culture object definition is different
//if (cultureSource.ObjectDefinition != cultureTarget.ObjectDefinition)
if ( (
(_comparisonInfo.OptionsInfo.OptionMergeCultures && cultureTarget.ContainsOtherCultureTranslations(cultureSource)) ||
(!_comparisonInfo.OptionsInfo.OptionMergeCultures && cultureTarget.ContainsOtherCultureTranslations(cultureSource) && cultureSource.ContainsOtherCultureTranslations(cultureTarget))
) &&
(sourceLinguisticMetadata == targetLinguisticMetadata)
)
{
// they are equal, ...
comparisonObjectCulture = new ComparisonObject(ComparisonObjectType.Culture, ComparisonObjectStatus.SameDefinition, cultureSource, cultureTarget, MergeAction.Skip);
_comparisonObjects.Add(comparisonObjectCulture);
_comparisonObjectCount += 1;
}
else
{
comparisonObjectCulture = new ComparisonObject(ComparisonObjectType.Culture, ComparisonObjectStatus.DifferentDefinitions, cultureSource, cultureTarget, MergeAction.Update);
_comparisonObjects.Add(comparisonObjectCulture);
_comparisonObjectCount += 1;
}
}
}
foreach (Culture cultureTarget in _targetTabularModel.Cultures)
{
// if target culture is Missing in Source, offer deletion
if (!_sourceTabularModel.Cultures.ContainsName(cultureTarget.Name))
{
ComparisonObject comparisonObjectCulture = new ComparisonObject(ComparisonObjectType.Culture, ComparisonObjectStatus.MissingInSource, null, cultureTarget, MergeAction.Delete);
_comparisonObjects.Add(comparisonObjectCulture);
_comparisonObjectCount += 1;
}
}
}
#endregion
#region Roles
if (_comparisonInfo.OptionsInfo.OptionRoles)
{
foreach (Role roleSource in _sourceTabularModel.Roles)
{
// check if source is not in target
if (!_targetTabularModel.Roles.ContainsName(roleSource.Name))
{
ComparisonObject comparisonObjectRole = new ComparisonObject(ComparisonObjectType.Role, ComparisonObjectStatus.MissingInTarget, roleSource, null, MergeAction.Create);
_comparisonObjects.Add(comparisonObjectRole);
_comparisonObjectCount += 1;
}
else
{
// there is a role in the target with the same name at least
Role roleTarget = _targetTabularModel.Roles.FindByName(roleSource.Name);
ComparisonObject comparisonObjectRole;
// check if role object definition is different
if (roleSource.ObjectDefinition != roleTarget.ObjectDefinition)
{
comparisonObjectRole = new ComparisonObject(ComparisonObjectType.Role, ComparisonObjectStatus.DifferentDefinitions, roleSource, roleTarget, MergeAction.Update);
_comparisonObjects.Add(comparisonObjectRole);
_comparisonObjectCount += 1;
}
else
{
// they are equal, ...
comparisonObjectRole = new ComparisonObject(ComparisonObjectType.Role, ComparisonObjectStatus.SameDefinition, roleSource, roleTarget, MergeAction.Skip);
_comparisonObjects.Add(comparisonObjectRole);
_comparisonObjectCount += 1;
}
}
}
foreach (Role roleTarget in _targetTabularModel.Roles)
{
// if target role is Missing in Source, offer deletion
if (!_sourceTabularModel.Roles.ContainsName(roleTarget.Name))
{
ComparisonObject comparisonObjectRole = new ComparisonObject(ComparisonObjectType.Role, ComparisonObjectStatus.MissingInSource, null, roleTarget, MergeAction.Delete);
_comparisonObjects.Add(comparisonObjectRole);
_comparisonObjectCount += 1;
}
}
}
#endregion
#region Sorting
_comparisonObjects.Sort();
foreach (ComparisonObject childComparisonObject in _comparisonObjects)
{
childComparisonObject.ChildComparisonObjects.Sort();
foreach (ComparisonObject grandChildComparisonObject in childComparisonObject.ChildComparisonObjects)
{
grandChildComparisonObject.ChildComparisonObjects.Sort();
}
}
#endregion
this.RefreshComparisonObjectsFromSkipSelections();
_uncommitedChanges = false;
_lastSourceSchemaUpdate = _sourceTabularModel.TomDatabase.LastSchemaUpdate;
_lastTargetSchemaUpdate = _targetTabularModel.TomDatabase.LastSchemaUpdate;
}
/// <summary>
/// Validate selection of actions to perform on target tabular model. Warnings and informational messages are provided by invoking ShowStatusMessageCallBack.
/// </summary>
public override void ValidateSelection()
{
#region Refresh/reconnect source and target dbs to check if server definition has changed
bool reconnect = false;
try
{
if (!_sourceTabularModel.ConnectionInfo.UseBimFile) _sourceTabularModel.TomDatabase.Refresh();
if (!_targetTabularModel.ConnectionInfo.UseBimFile) _targetTabularModel.TomDatabase.Refresh();
}
catch (Exception)
{
reconnect = true;
}
if (reconnect || _uncommitedChanges)
{
// Reconnect to re-initialize
_sourceTabularModel = new TabularModel(this, _comparisonInfo.ConnectionInfoSource, _comparisonInfo);
_sourceTabularModel.Connect();
_targetTabularModel = new TabularModel(this, _comparisonInfo.ConnectionInfoTarget, _comparisonInfo);
_targetTabularModel.Connect();
}
if (!_sourceTabularModel.ConnectionInfo.UseProject && _sourceTabularModel.TomDatabase.LastSchemaUpdate > _lastSourceSchemaUpdate)
{
throw new Exception("The definition of the source database has changed since the comparison was run. Please re-run the comparison.");
}
if (!_targetTabularModel.ConnectionInfo.UseProject && _targetTabularModel.TomDatabase.LastSchemaUpdate > _lastTargetSchemaUpdate)
{
throw new Exception("The definition of the target database has changed since the comparison was run. Please re-run the comparison.");
}
_uncommitedChanges = true;
#endregion
#region Iterate of objects for delete/create/updates
#region Backup perspectives, cultures and roles
/*It's easier to take a backup of perspectives, cultures and roles now and add back after table changes, rather than every
time update a table, take a temp backup to add back columns/measures. Also would need to remove deleted tables/meausures, ...
Gets pretty hairy.
*/
_targetTabularModel.BackupAffectedObjects();
#endregion
#region DataSources
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
DeleteDataSource(comparisonObject);
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
CreateDataSource(comparisonObject);
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
UpdateDataSource(comparisonObject);
}
#endregion
#region Expressions
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
DeleteExpression(comparisonObject);
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
CreateExpression(comparisonObject);
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
UpdateExpression(comparisonObject);
}
#endregion
#region Model1
//Doing before tables in case need to set DiscourageImplicitMeasures=true to create calc groups downstream
bool updatedModel = false;
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
if (UpdateModel(comparisonObject, true))
{
updatedModel = true;
break;
}
}
#endregion
#region Tables
// do deletions first to minimize chance of conflict
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
DeleteTable(comparisonObject);
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
CreateTable(comparisonObject);
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
UpdateTable(comparisonObject);
}
#endregion
#region Relationships
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects)
{
DeleteRelationship(childComparisonObject); //Relationship
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects)
{
CreateRelationship(childComparisonObject, comparisonObject.SourceObjectName); //Relationship, Table
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects)
{
UpdateRelationship(childComparisonObject, comparisonObject.SourceObjectName); //Relationship, Table
}
}
_targetTabularModel.ValidateRelationships();
#endregion
_targetTabularModel.CleanUpVariations();
#region Model2
//Doing model after tables in case there are calc group tables created so cannot set DisableImplictMeasures=false
if (!updatedModel)
{
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
if (UpdateModel(comparisonObject, false))
break;
}
}
#endregion
#region Measures / KPIs
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects)
{
DeleteMeasure(childComparisonObject); //Measure
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects)
{
CreateMeasure(childComparisonObject, comparisonObject.SourceObjectName); //Measure, Table
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects)
{
UpdateMeasure(childComparisonObject, comparisonObject.SourceObjectName); //Measure, Table
}
}
#endregion
#region CalculationItems
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects)
{
DeleteCalculationItem(childComparisonObject, comparisonObject.SourceObjectName); //CalculationItem, Table
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects)
{
CreateCalculationItem(childComparisonObject, comparisonObject.SourceObjectName); //CalculationItem, Table
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects)
{
UpdateCalculationItem(childComparisonObject, comparisonObject.SourceObjectName); //CalculationItem, Table
}
}
#endregion
#region Perspectives
//Restore perspectives that were backed up earlier. Having done this there won't be any dependency issues, so can start comparison changes.
_targetTabularModel.RestorePerspectives();
// Do separate loops of _comparisonObjectects for Delete, Create, Update to ensure informational logging order is consistent with other object types. This also ensures deletions are done first to minimize chance of conflict.
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Perspective && comparisonObject.MergeAction == MergeAction.Delete)
{
_targetTabularModel.DeletePerspective(comparisonObject.TargetObjectInternalName);
OnValidationMessage(new ValidationMessageEventArgs($"Delete perspective [{comparisonObject.TargetObjectName}].", ValidationMessageType.Perspective, ValidationMessageStatus.Informational));
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Perspective && comparisonObject.MergeAction == MergeAction.Create)
{
_targetTabularModel.CreatePerspective(_sourceTabularModel.Perspectives.FindById(comparisonObject.SourceObjectInternalName).TomPerspective);
OnValidationMessage(new ValidationMessageEventArgs($"Create perspective [{comparisonObject.SourceObjectName}].", ValidationMessageType.Perspective, ValidationMessageStatus.Informational));
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Perspective && comparisonObject.MergeAction == MergeAction.Update)
{
_targetTabularModel.UpdatePerspective(_sourceTabularModel.Perspectives.FindById(comparisonObject.SourceObjectInternalName).TomPerspective, _targetTabularModel.Perspectives.FindById(comparisonObject.TargetObjectInternalName).TomPerspective);
OnValidationMessage(new ValidationMessageEventArgs($"Update perspective [{comparisonObject.TargetObjectName}].", ValidationMessageType.Perspective, ValidationMessageStatus.Informational));
}
}
#endregion
#region Roles
//Restore roles that were backed up earlier. Having done this there won't be any dependency issues, so can start comparison changes.
_targetTabularModel.RestoreRoles();
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Role && comparisonObject.MergeAction == MergeAction.Delete)
{
_targetTabularModel.DeleteRole(comparisonObject.TargetObjectInternalName);
OnValidationMessage(new ValidationMessageEventArgs($"Delete role [{comparisonObject.TargetObjectName}].", ValidationMessageType.Role, ValidationMessageStatus.Informational));
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Role && comparisonObject.MergeAction == MergeAction.Create)
{
_targetTabularModel.CreateRole(_sourceTabularModel.Roles.FindById(comparisonObject.SourceObjectInternalName).TomRole);
OnValidationMessage(new ValidationMessageEventArgs($"Create role [{comparisonObject.SourceObjectName}].", ValidationMessageType.Role, ValidationMessageStatus.Informational));
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Role && comparisonObject.MergeAction == MergeAction.Update)
{
_targetTabularModel.UpdateRole(_sourceTabularModel.Roles.FindById(comparisonObject.SourceObjectInternalName), _targetTabularModel.Roles.FindById(comparisonObject.TargetObjectInternalName));
OnValidationMessage(new ValidationMessageEventArgs($"Update role [{comparisonObject.TargetObjectName}].", ValidationMessageType.Role, ValidationMessageStatus.Informational));
}
}
_targetTabularModel.RolesCleanup();
#endregion
_targetTabularModel.CleanUpAggregations();
#region Cultures
//Restore cultures that were backed up earlier. Having done this there won't be any dependency issues, so can start comparison changes.
//Note that cannot restore cultures before finished perspective comparison changes above, because cultures can have dependencies on perspectives.
_targetTabularModel.RestoreCultues();
// Do separate loops of _comparisonObjectects for Delete, Create, Update to ensure informational logging order is consistent with other object types. This also ensures deletions are done first to minimize chance of conflict.
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Culture && comparisonObject.MergeAction == MergeAction.Delete)
{
_targetTabularModel.DeleteCulture(comparisonObject.TargetObjectInternalName);
OnValidationMessage(new ValidationMessageEventArgs($"Delete culture [{comparisonObject.TargetObjectName}].", ValidationMessageType.Culture, ValidationMessageStatus.Informational));
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Culture && comparisonObject.MergeAction == MergeAction.Create)
{
_targetTabularModel.CreateCulture(_sourceTabularModel.Cultures.FindById(comparisonObject.SourceObjectInternalName).TomCulture);
OnValidationMessage(new ValidationMessageEventArgs($"Create culture [{comparisonObject.SourceObjectName}].", ValidationMessageType.Culture, ValidationMessageStatus.Informational));
}
}
foreach (ComparisonObject comparisonObject in _comparisonObjects)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Culture && comparisonObject.MergeAction == MergeAction.Update)
{
_targetTabularModel.UpdateCulture(_sourceTabularModel.Cultures.FindById(comparisonObject.SourceObjectInternalName).TomCulture, _targetTabularModel.Cultures.FindById(comparisonObject.TargetObjectInternalName).TomCulture);
OnValidationMessage(new ValidationMessageEventArgs($"Update culture [{comparisonObject.TargetObjectName}].", ValidationMessageType.Culture, ValidationMessageStatus.Informational));
}
}
#endregion
#endregion
#region Missing measure dependencies
if (_comparisonInfo.OptionsInfo.OptionMeasureDependencies)
{
foreach (Table table in _targetTabularModel.Tables)
{
foreach (Measure measure in table.Measures)
{
foreach (string missingDependency in measure.FindMissingMeasureDependencies())
{
OnValidationMessage(new ValidationMessageEventArgs($"Measure [{measure.InternalName}] in table '{table.Name}' contains dependency on measure/column [{missingDependency}], which (considering changes to target) cannot be found in target model.", ValidationMessageType.MeasureCalculationDependency, ValidationMessageStatus.Informational));
}
}
}
}
#endregion
OnResizeValidationHeaders(new EventArgs());
}
#region Private methods for validation
#region Calc dependencies validation
private bool HasBlockingToDependenciesInTarget(string targetObjectName, string referencedTableName, CalcDependencyObjectType targetObjectType, ref List<string> warningObjectList)
{
//For deletion.
//Check any objects in target that depend on this object are also going to be deleted or updated.
bool returnVal = false;
CalcDependencyCollection targetToDepdendencies = _targetTabularModel.MDependencies.DependenciesReferenceTo(targetObjectType, targetObjectName, referencedTableName);
foreach (CalcDependency targetToDependency in targetToDepdendencies)
{
foreach (ComparisonObject comparisonObjectToCheck in _comparisonObjects)
{
switch (targetToDependency.ObjectType)
{
case CalcDependencyObjectType.Expression:
//Does this expression (comparisonObjectToCheck) have a dependency on the object about to be deleted (targetObjectName)?
if (comparisonObjectToCheck.ComparisonObjectType == ComparisonObjectType.Expression &&
comparisonObjectToCheck.TargetObjectName == targetToDependency.ObjectName &&
(
comparisonObjectToCheck.MergeAction == MergeAction.Skip || //Skip covers if this expression is for deletion and being skipped, or if same defintion and not being touched in target (in either case, dependency will remain).
(
comparisonObjectToCheck.MergeAction == MergeAction.Update && //Updates (if successful) are fine because covered by source dependency checking. So need to check if the update will be unsuccessful (and therefore dependency will remain).
HasBlockingFromDependenciesInSource(
"", //Expressions don't have table value
comparisonObjectToCheck.TargetObjectName,
CalcDependencyObjectType.Expression)
) //Create expression is not possible to have a dependency on this object about to be deleted. Delete expression is fine.
)
)
{
string warningObject = $"Expression {comparisonObjectToCheck.TargetObjectName}";
if (!warningObjectList.Contains(warningObject))
{
warningObjectList.Add(warningObject);
}
returnVal = true;
}
break;
case CalcDependencyObjectType.Partition:
//Does this table (comparisonObjectToCheck) have a dependency on the object about to be deleted (targetObjectName)?
if (comparisonObjectToCheck.ComparisonObjectType == ComparisonObjectType.Table &&
comparisonObjectToCheck.TargetObjectName == targetToDependency.TableName &&
(
comparisonObjectToCheck.MergeAction == MergeAction.Skip || //Skip covers if this table is for deletion and being skipped, or if same defintion and not being touched in target (in either case, dependency will remain).
(
comparisonObjectToCheck.MergeAction == MergeAction.Update && //Updates (if successful) are fine because covered by source dependency checking. So need to check if the update will be unsuccessful (and therefore dependency will remain).
(
HasBlockingFromDependenciesInSourceForTable(_sourceTabularModel.Tables.FindByName(comparisonObjectToCheck.TargetObjectName)) ||
_targetTabularModel.CanRetainPartitions( //But also check if doing retain partitions on this table (if so, dependency will remain).
_sourceTabularModel.Tables.FindByName(comparisonObjectToCheck.TargetObjectName),
_targetTabularModel.Tables.FindByName(comparisonObjectToCheck.TargetObjectName),
out string retainPartitionsMessage,
out PartitionSourceType partitionSourceTypeSource,
out PartitionSourceType partitionSourceTypeTarget)
)
) //Create table is not possible to have a dependency on this object about to be deleted. Delete table is fine.
)
)
{
string warningObject = $"Table {comparisonObjectToCheck.TargetObjectName}/Partition {targetToDependency.ObjectName}";
if (!warningObjectList.Contains(warningObject))
{
warningObjectList.Add(warningObject);
}
returnVal = true;
}
break;
default:
break;
}
}
}
return returnVal;
}
private bool HasBlockingFromDependenciesInSource(string sourceTableName, string sourceObjectName, CalcDependencyObjectType sourceObjectType, ref List<string> warningObjectList, out bool nonStructuredDataSource)
{
//For creation and updates.
//Check any objects in source that this object depends on are also going to be created OR updated (if not already in target).
bool returnVal = false;
nonStructuredDataSource = false;
CalcDependencyCollection sourceFromDepdendencies = _sourceTabularModel.MDependencies.DependenciesReferenceFrom(sourceObjectType, sourceTableName, sourceObjectName);
foreach (CalcDependency sourceFromDependency in sourceFromDepdendencies)
{
foreach (ComparisonObject comparisonObjectToCheck in _comparisonObjects)
{
switch (sourceFromDependency.ReferencedObjectType)
{
case CalcDependencyObjectType.Expression:
//Does the object about to be created/updated (sourceObjectName) have a source dependency on this expression (comparisonObjectToCheck)?
if (!_targetTabularModel.Expressions.ContainsName(sourceFromDependency.ReferencedObjectName) &&
comparisonObjectToCheck.ComparisonObjectType == ComparisonObjectType.Expression &&
comparisonObjectToCheck.SourceObjectName == sourceFromDependency.ReferencedObjectName &&
comparisonObjectToCheck.Status == ComparisonObjectStatus.MissingInTarget && //Creates being skipped (dependency will be missing).
comparisonObjectToCheck.MergeAction == MergeAction.Skip)
//Deletes are impossible for this object to depend on, so don't need to detect. Other Skips can assume are fine, so don't need to detect.
{
string warningObject = $"Expression {comparisonObjectToCheck.SourceObjectName}";
if (!warningObjectList.Contains(warningObject))
{
warningObjectList.Add(warningObject);
}
returnVal = true;
}
break;
case CalcDependencyObjectType.Partition:
//Does the object about to be created/updated (sourceObjectName) have a source dependency on this table (comparisonObjectToCheck)?
if (!_targetTabularModel.Tables.ContainsName(sourceFromDependency.ReferencedTableName) &&
comparisonObjectToCheck.ComparisonObjectType == ComparisonObjectType.Table &&
comparisonObjectToCheck.SourceObjectName == sourceFromDependency.ReferencedTableName &&
comparisonObjectToCheck.Status == ComparisonObjectStatus.MissingInTarget && //Creates being skipped (dependency will be missing).
comparisonObjectToCheck.MergeAction == MergeAction.Skip)
//Deletes are impossible for this object to depend on, so don't need to detect. Other Skips can assume are fine, so don't need to detect.
{
string warningObject = $"Table {comparisonObjectToCheck.SourceObjectName}";
if (!warningObjectList.Contains(warningObject))
{
warningObjectList.Add(warningObject);
}
returnVal = true;
}
break;
case CalcDependencyObjectType.DataSource:
//Does the object about to be created/updated (sourceObjectName) have a source dependency on this data source (comparisonObjectToCheck)?
if (!_targetTabularModel.DataSources.ContainsName(sourceFromDependency.ReferencedObjectName) &&
comparisonObjectToCheck.ComparisonObjectType == ComparisonObjectType.DataSource &&
comparisonObjectToCheck.SourceObjectName == sourceFromDependency.ReferencedObjectName &&
comparisonObjectToCheck.Status == ComparisonObjectStatus.MissingInTarget && //Creates being skipped (dependency will be missing).
comparisonObjectToCheck.MergeAction == MergeAction.Skip)
//Deletes are impossible for this object to depend on, so don't need to detect. Other Skips can assume are fine, so don't need to detect.
{
string warningObject = $"Data Source {comparisonObjectToCheck.SourceObjectName}";
if (!warningObjectList.Contains(warningObject))
{
warningObjectList.Add(warningObject);
}
returnVal = true;
}
//Check if target data source type is provider and source is structured. Won't be updated.
if (comparisonObjectToCheck.ComparisonObjectType == ComparisonObjectType.DataSource &&
comparisonObjectToCheck.Status == ComparisonObjectStatus.DifferentDefinitions &&
comparisonObjectToCheck.SourceObjectName == sourceFromDependency.ReferencedObjectName &&
_targetTabularModel.DataSources.ContainsName(sourceFromDependency.ReferencedObjectName) &&
_sourceTabularModel.DataSources.ContainsName(sourceFromDependency.ReferencedObjectName) &&
_targetTabularModel.DataSources.FindByName(sourceFromDependency.ReferencedObjectName).TomDataSource.Type == DataSourceType.Provider &&
_sourceTabularModel.DataSources.FindByName(sourceFromDependency.ReferencedObjectName).TomDataSource.Type == DataSourceType.Structured) //Don't need to check if Skip or not because can't update if different data source types anyway
{
string warningObject = $"Data Source {comparisonObjectToCheck.SourceObjectName}";
if (!warningObjectList.Contains(warningObject))
{
warningObjectList.Add(warningObject);
}
returnVal = true;
nonStructuredDataSource = true;
}
break;
default:
break;
}
}
}
return returnVal;
}
private bool HasBlockingFromDependenciesInSource(string sourceTableName, string sourceObjectName, CalcDependencyObjectType sourceObjectType)
{
List<string> warningObjectList = new List<string>();
return HasBlockingFromDependenciesInSource(sourceTableName, sourceObjectName, sourceObjectType, ref warningObjectList, out bool nonStructuredDataSource);
}
private bool HasBlockingFromDependenciesInSourceForTable(Table sourceTable)
{
bool returnVal = false;
foreach (Partition partition in sourceTable.TomTable.Partitions)
{
if (HasBlockingFromDependenciesInSource(sourceTable.Name, partition.Name, CalcDependencyObjectType.Partition))
{
returnVal = true;
break;
}
}
return returnVal;
}
private bool HasBlockingOldPartitionDependency(Partition partition, ref List<string> warningObjectList)
{
//Only for old partition types
bool returnVal = false;
if (partition.SourceType == PartitionSourceType.Query &&
!_targetTabularModel.DataSources.ContainsName(((QueryPartitionSource)partition.Source).DataSource.Name))
{
string dataSourceName = ((QueryPartitionSource)partition.Source).DataSource.Name;
//For old non-M partitions, check if data source references exist
foreach (ComparisonObject comparisonObjectToCheck in _comparisonObjects)
{
if (comparisonObjectToCheck.ComparisonObjectType == ComparisonObjectType.DataSource &&
comparisonObjectToCheck.SourceObjectName == dataSourceName &&
comparisonObjectToCheck.Status == ComparisonObjectStatus.MissingInTarget &&
comparisonObjectToCheck.MergeAction == MergeAction.Skip)
{
string warningObject = $"Data Source {dataSourceName}";
if (!warningObjectList.Contains(warningObject))
{
warningObjectList.Add(warningObject);
}
returnVal = true;
}
}
}
return returnVal;
}
#endregion
#region Model
private bool UpdateModel(ComparisonObject comparisonObject, bool beforeTables)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Model && comparisonObject.MergeAction == MergeAction.Update)
{
Model sourceModel = _sourceTabularModel.Model;
Model targetModel = _targetTabularModel.Model;
bool targetHasCalcGroups = false;
foreach (Table table in _targetTabularModel.Tables)
{
if (table.IsCalculationGroup)
{
targetHasCalcGroups = true;
break;
}
}
if (beforeTables)
{
//In this case, may need to create calc groups downstream, so may need to set DiscourageImplicitMeasures to true
if (!targetHasCalcGroups && sourceModel.TomModel.DiscourageImplicitMeasures)
{
_targetTabularModel.UpdateModel(sourceModel, targetModel);
OnValidationMessage(new ValidationMessageEventArgs($"Update model.", ValidationMessageType.Model, ValidationMessageStatus.Informational));
return true;
}
}
else
{
//In this case, have already had chance to create/delete calc groups, so OK to disable implicit measures if able
if (targetHasCalcGroups && sourceModel.TomModel.DiscourageImplicitMeasures == false)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to update model because (considering changes) the target has calculation group(s) and the source has DiscourageImplicitMeasures set to false.", ValidationMessageType.Model, ValidationMessageStatus.Warning));
}
else
{
_targetTabularModel.UpdateModel(sourceModel, targetModel);
OnValidationMessage(new ValidationMessageEventArgs($"Update model.", ValidationMessageType.Model, ValidationMessageStatus.Informational));
return true;
}
}
}
return false;
}
#endregion
#region DataSources
private void DeleteDataSource(ComparisonObject comparisonObject)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.DataSource && comparisonObject.MergeAction == MergeAction.Delete)
{
if (!DesktopHardened(comparisonObject, ValidationMessageType.DataSource))
{
return;
};
//Check any objects in target that depend on the DataSource are also going to be deleted
List<string> warningObjectList = new List<string>();
bool toDependencies = HasBlockingToDependenciesInTarget(comparisonObject.TargetObjectName, "", CalcDependencyObjectType.DataSource, ref warningObjectList);
//For old non-M partitions, check if any such tables have reference to this DataSource, and will not be deleted
foreach (Table table in _targetTabularModel.Tables)
{
foreach (Partition partition in table.TomTable.Partitions)
{
if (partition.SourceType == PartitionSourceType.Query &&
table.DataSourceName == comparisonObject.TargetObjectName)
{
foreach (ComparisonObject comparisonObjectToCheck in _comparisonObjects)
{
if (
(
comparisonObjectToCheck.ComparisonObjectType == ComparisonObjectType.Table &&
comparisonObjectToCheck.TargetObjectName == table.Name
) &&
(
( //Skipped deletes, dependency will remain
comparisonObjectToCheck.Status == ComparisonObjectStatus.MissingInSource &&
comparisonObjectToCheck.MergeAction == MergeAction.Skip
) ||
( //Same definition, dependency will remain
comparisonObjectToCheck.Status == ComparisonObjectStatus.SameDefinition
) ||
( //Different definition, and skip update (source already dependencies covered), dependency will remain
comparisonObjectToCheck.Status == ComparisonObjectStatus.DifferentDefinitions &&
comparisonObjectToCheck.MergeAction == MergeAction.Skip
)
)
)
{
string warningObject = $"Table {table.Name}/Partition {partition.Name}";
if (!warningObjectList.Contains(warningObject))
{
warningObjectList.Add(warningObject);
}
toDependencies = true;
}
}
}
}
}
if (!toDependencies)
{
_targetTabularModel.DeleteDataSource(comparisonObject.TargetObjectName);
OnValidationMessage(new ValidationMessageEventArgs($"Delete data source [{comparisonObject.TargetObjectName}].", ValidationMessageType.DataSource, ValidationMessageStatus.Informational));
}
else
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to delete data source {comparisonObject.TargetObjectName} because the following object(s) depend on it: {String.Join(", ", warningObjectList)}.", ValidationMessageType.DataSource, ValidationMessageStatus.Warning));
}
}
}
private void CreateDataSource(ComparisonObject comparisonObject)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.DataSource && comparisonObject.MergeAction == MergeAction.Create)
{
if (!DesktopHardened(comparisonObject, ValidationMessageType.DataSource))
{
return;
};
_targetTabularModel.CreateDataSource(_sourceTabularModel.DataSources.FindByName(comparisonObject.SourceObjectName));
OnValidationMessage(new ValidationMessageEventArgs($"Create data source [{comparisonObject.SourceObjectName}].", ValidationMessageType.DataSource, ValidationMessageStatus.Informational));
}
}
private void UpdateDataSource(ComparisonObject comparisonObject)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.DataSource && comparisonObject.MergeAction == MergeAction.Update)
{
if (!DesktopHardened(comparisonObject, ValidationMessageType.DataSource))
{
return;
};
DataSource sourceDataSource = _sourceTabularModel.DataSources.FindByName(comparisonObject.SourceObjectName);
DataSource targetDataSource = _targetTabularModel.DataSources.FindByName(comparisonObject.TargetObjectName);
if (sourceDataSource.TomDataSource.Type != targetDataSource.TomDataSource.Type)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to update data source {comparisonObject.TargetObjectName} because the source/target types (provider/structured) don't match, which is not supported.", ValidationMessageType.DataSource, ValidationMessageStatus.Warning));
}
else
{
_targetTabularModel.UpdateDataSource(sourceDataSource, targetDataSource);
OnValidationMessage(new ValidationMessageEventArgs($"Update data source [{comparisonObject.TargetObjectName}].", ValidationMessageType.DataSource, ValidationMessageStatus.Informational));
}
}
}
#endregion
#region Expressions
private void DeleteExpression(ComparisonObject comparisonObject)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Expression && comparisonObject.MergeAction == MergeAction.Delete)
{
if (!DesktopHardened(comparisonObject, ValidationMessageType.Expression))
{
return;
};
//Check if incremental refresh param and there is an incremental refresh table in the model
if (comparisonObject.TargetObjectName == "RangeStart" || comparisonObject.TargetObjectName == "RangeEnd")
{
foreach (Table table in _targetTabularModel.Tables)
{
if (table.TomTable.RefreshPolicy != null)
{
//Confirm the table with incremental refresh policy isn't going to be deleted anyway
bool tableBeingDeletedAnyway = false;
foreach (ComparisonObject comparisonObjectToCheck in _comparisonObjects)
{
if (comparisonObjectToCheck.TargetObjectName == table.Name && comparisonObjectToCheck.MergeAction == MergeAction.Delete)
{
tableBeingDeletedAnyway = true;
break;
}
}
if (!tableBeingDeletedAnyway)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to delete expression {comparisonObject.TargetObjectName} because it is an incremental-refresh parameter and table {table.Name} contains an incremental-refresh policy.", ValidationMessageType.Expression, ValidationMessageStatus.Warning));
return;
}
}
}
}
//Check any objects in target that depend on the expression are also going to be deleted
List<string> warningObjectList = new List<string>();
if (!HasBlockingToDependenciesInTarget(comparisonObject.TargetObjectName, "", CalcDependencyObjectType.Expression, ref warningObjectList))
{
_targetTabularModel.DeleteExpression(comparisonObject.TargetObjectName);
OnValidationMessage(new ValidationMessageEventArgs($"Delete expression [{comparisonObject.TargetObjectName}].", ValidationMessageType.Expression, ValidationMessageStatus.Informational));
}
else
{
string message = $"Unable to delete expression {comparisonObject.TargetObjectName} because the following objects depend on it: {String.Join(", ", warningObjectList)}.";
if (_comparisonInfo.OptionsInfo.OptionRetainPartitions && !_comparisonInfo.OptionsInfo.OptionRetainPolicyPartitions)
{
message += " Note: the option to retain partitions is on, which may be affecting this.";
}
OnValidationMessage(new ValidationMessageEventArgs(message, ValidationMessageType.Expression, ValidationMessageStatus.Warning));
}
}
}
private void CreateExpression(ComparisonObject comparisonObject)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Expression && comparisonObject.MergeAction == MergeAction.Create)
{
if (!DesktopHardened(comparisonObject, ValidationMessageType.Expression))
{
return;
};
//Check any objects in source that this expression depends on are also going to be created if not already in target
List<string> warningObjectList = new List<string>();
if (!HasBlockingFromDependenciesInSource(
"", //can assume blank table for an expression
comparisonObject.SourceObjectName,
CalcDependencyObjectType.Expression,
ref warningObjectList,
out bool nonStructuredDataSource))
{
_targetTabularModel.CreateExpression(_sourceTabularModel.Expressions.FindByName(comparisonObject.SourceObjectName).TomExpression);
OnValidationMessage(new ValidationMessageEventArgs($"Create expression [{comparisonObject.SourceObjectName}].", ValidationMessageType.Expression, ValidationMessageStatus.Informational));
}
else
{
if (!nonStructuredDataSource)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to create expression {comparisonObject.SourceObjectName} because it depends on the following objects, which (considering changes) are missing from target: {String.Join(", ", warningObjectList)}.", ValidationMessageType.Expression, ValidationMessageStatus.Warning));
}
else
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to create expression {comparisonObject.SourceObjectName} because it depends on the following objects, which (considering changes) are missing from target and/or depend on a structured data source that is provider in the target: {String.Join(", ", warningObjectList)}.", ValidationMessageType.Expression, ValidationMessageStatus.Warning));
}
}
}
}
private void UpdateExpression(ComparisonObject comparisonObject)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Expression && comparisonObject.MergeAction == MergeAction.Update)
{
if (!DesktopHardened(comparisonObject, ValidationMessageType.Expression))
{
return;
};
//Check any objects in source that this expression depends on are also going to be created if not already in target
List<string> warningObjectList = new List<string>();
if (!HasBlockingFromDependenciesInSource(
"", //Can assume blank table for expression
comparisonObject.SourceObjectName,
CalcDependencyObjectType.Expression,
ref warningObjectList,
out bool nonStructuredDataSource))
{
_targetTabularModel.UpdateExpression(_sourceTabularModel.Expressions.FindByName(comparisonObject.SourceObjectName), _targetTabularModel.Expressions.FindByName(comparisonObject.TargetObjectName));
OnValidationMessage(new ValidationMessageEventArgs($"Update expression [{comparisonObject.TargetObjectName}].", ValidationMessageType.Expression, ValidationMessageStatus.Informational));
}
else
{
if (!nonStructuredDataSource)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to update expression {comparisonObject.TargetObjectName} because version from the source depends on the following objects, which (considering changes) are missing from target: {String.Join(", ", warningObjectList)}.", ValidationMessageType.Expression, ValidationMessageStatus.Warning));
}
else
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to update expression {comparisonObject.TargetObjectName} because version from the source depends on the following objects, which (considering changes) are missing from target and/or depend on a structured data source that is provider in the target: {String.Join(", ", warningObjectList)}.", ValidationMessageType.Expression, ValidationMessageStatus.Warning));
}
}
}
}
#endregion
#region Tables
private void DeleteTable(ComparisonObject comparisonObject)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Table && comparisonObject.MergeAction == MergeAction.Delete)
{
Table targetTable = _targetTabularModel.Tables.FindByName(comparisonObject.TargetObjectName);
bool isCalculationGroup = false;
bool isCalcTable = false;
if (targetTable != null)
{
isCalculationGroup = targetTable.IsCalculationGroup;
isCalcTable = (targetTable.TomTable.Partitions.Count > 0 && targetTable.TomTable.Partitions[0].SourceType == PartitionSourceType.Calculated);
}
if (!isCalculationGroup && !isCalcTable && !DesktopHardened(comparisonObject, ValidationMessageType.Table))
{
return;
};
//Check any objects in target that depend on the table expression are also going to be deleted
List<string> warningObjectList = new List<string>();
if (!HasBlockingToDependenciesInTarget("", comparisonObject.TargetObjectName, CalcDependencyObjectType.Partition, ref warningObjectList))
{
_targetTabularModel.DeleteTable(comparisonObject.TargetObjectName);
OnValidationMessage(new ValidationMessageEventArgs($"Delete {(isCalculationGroup ? "calculation group" : "table")} '{comparisonObject.TargetObjectName}'.", ValidationMessageType.Table, ValidationMessageStatus.Informational));
}
else
{
string message = $"Unable to delete table {comparisonObject.TargetObjectName} because the following objects depend on it: {String.Join(", ", warningObjectList)}.";
if (_comparisonInfo.OptionsInfo.OptionRetainPartitions && !_comparisonInfo.OptionsInfo.OptionRetainPolicyPartitions)
{
message += " Note: the option to retain partitions is on, which may be affecting this.";
}
OnValidationMessage(new ValidationMessageEventArgs(message, ValidationMessageType.Table, ValidationMessageStatus.Warning));
}
}
}
private void CreateTable(ComparisonObject comparisonObject)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Table && comparisonObject.MergeAction == MergeAction.Create)
{
Table sourceTable = _sourceTabularModel.Tables.FindByName(comparisonObject.SourceObjectName);
List<string> warningObjectList = new List<string>();
bool fromDependencies = false;
bool nonStructuredDataSourceLocal = false;
if (!sourceTable.IsCalculationGroup)
{
foreach (Partition partition in sourceTable.TomTable.Partitions)
{
//Check any objects in source that this partition depends on are also going to be created if not already in target
if (HasBlockingFromDependenciesInSource(sourceTable.Name, partition.Name, CalcDependencyObjectType.Partition, ref warningObjectList, out bool nonStructuredDataSource))
{
fromDependencies = true;
if (nonStructuredDataSource)
nonStructuredDataSourceLocal = true;
}
//For old non-M partitions, check if data source references exist
if (HasBlockingOldPartitionDependency(partition, ref warningObjectList))
fromDependencies = true; //Need if clause in case last of n partitions has no dependencies and sets back to true
}
}
if (!fromDependencies)
{
if (sourceTable.IsCalculationGroup)
{
if (_targetTabularModel.Model.TomModel.DiscourageImplicitMeasures != true)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to create calculation group {comparisonObject.SourceObjectName} because the target model doesn't have DiscourageImplicitMeasures set to true.", ValidationMessageType.Table, ValidationMessageStatus.Warning));
}
else
{
_targetTabularModel.CreateTable(sourceTable);
OnValidationMessage(new ValidationMessageEventArgs($"Create calculation group '{comparisonObject.SourceObjectName}'.", ValidationMessageType.Table, ValidationMessageStatus.Informational));
}
}
else
{
bool isCalcTable = (sourceTable.TomTable.Partitions.Count > 0 && sourceTable.TomTable.Partitions[0].SourceType == PartitionSourceType.Calculated);
if (!isCalcTable && !DesktopHardened(comparisonObject, ValidationMessageType.Table))
{
return;
};
_targetTabularModel.CreateTable(sourceTable);
OnValidationMessage(new ValidationMessageEventArgs($"Create table '{comparisonObject.SourceObjectName}'.", ValidationMessageType.Table, ValidationMessageStatus.Informational));
}
}
else
{
if (!nonStructuredDataSourceLocal)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to create table {comparisonObject.SourceObjectName} because it depends on the following objects, which (considering changes) are missing from target: {String.Join(", ", warningObjectList)}.", ValidationMessageType.Table, ValidationMessageStatus.Warning));
}
else
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to create table {comparisonObject.SourceObjectName} because it depends on the following objects, which (considering changes) are missing from target and/or depend on a structured data source that is provider in the target: {String.Join(", ", warningObjectList)}.", ValidationMessageType.Table, ValidationMessageStatus.Warning));
}
}
}
}
private void UpdateTable(ComparisonObject comparisonObject)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Table && comparisonObject.MergeAction == MergeAction.Update)
{
Table tableSource = _sourceTabularModel.Tables.FindByName(comparisonObject.SourceObjectName);
Table tableTarget = _targetTabularModel.Tables.FindByName(comparisonObject.TargetObjectName);
List<string> warningObjectList = new List<string>();
bool fromDependencies = false;
bool nonStructuredDataSourceLocal = false;
bool canRetainPartitions =
_targetTabularModel.CanRetainPartitions(
tableSource, tableTarget,
out string retainPartitionsMessageTemp,
out PartitionSourceType partitionSourceTypeSource,
out PartitionSourceType partitionSourceTypeTarget);
//Will this table retain partitions? If yes, don't need to bother with source dependency (target dependency checking will cover for deletes).
if (!canRetainPartitions)
{
//Check any objects in source that this table depends on are also going to be created/updated if not already in target
foreach (Partition partition in tableSource.TomTable.Partitions)
{
if (HasBlockingFromDependenciesInSource(tableSource.Name, partition.Name, CalcDependencyObjectType.Partition, ref warningObjectList, out bool nonStructuredDataSource))
{
fromDependencies = true;
if (nonStructuredDataSource)
nonStructuredDataSourceLocal = true;
}
//For old non-M partitions, check if data source references exist
if (HasBlockingOldPartitionDependency(partition, ref warningObjectList))
fromDependencies = true; //Need if clause in case last of n partitions has no dependencies and sets back to true
}
}
if (!fromDependencies)
{
if (tableSource.IsCalculationGroup != tableTarget.IsCalculationGroup)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to update table {comparisonObject.TargetObjectName} because either source or target is a calculation group (but not both).", (tableSource.IsCalculationGroup ? ValidationMessageType.CalculationGroup : ValidationMessageType.Table), ValidationMessageStatus.Warning));
}
else
{
bool isCalcTable = (tableSource.TomTable.Partitions.Count > 0 && tableSource.TomTable.Partitions[0].SourceType == PartitionSourceType.Calculated);
if (!tableSource.IsCalculationGroup && !isCalcTable && !DesktopHardened(comparisonObject, ValidationMessageType.Table))
{
return;
};
//Check if, based on options selected, check if target table would contain policy based partitions with no refresh policy
if (
(canRetainPartitions && !_comparisonInfo.OptionsInfo.OptionRetainRefreshPolicy && partitionSourceTypeTarget == PartitionSourceType.PolicyRange && tableSource.TomTable.RefreshPolicy == null) ||
(!canRetainPartitions && _comparisonInfo.OptionsInfo.OptionRetainRefreshPolicy && partitionSourceTypeSource == PartitionSourceType.PolicyRange && tableTarget.TomTable.RefreshPolicy == null)
)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to update table {comparisonObject.TargetObjectName} because, based on options selected, the resulting table would contain policy based partitions with no refresh policy, which is not allowed.", (tableSource.IsCalculationGroup ? ValidationMessageType.CalculationGroup : ValidationMessageType.Table), ValidationMessageStatus.Warning));
}
else
{
_targetTabularModel.UpdateTable(tableSource, tableTarget, out string retainPartitionsMessage);
OnValidationMessage(new ValidationMessageEventArgs($"Update {(tableSource.IsCalculationGroup ? "calculation group" : "table")} '{comparisonObject.TargetObjectName}'. {retainPartitionsMessage}", ValidationMessageType.Table, ValidationMessageStatus.Informational));
}
}
}
else
{
if (!nonStructuredDataSourceLocal)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to update table {comparisonObject.TargetObjectName} because version from the source depends on the following objects, which (considering changes) are missing from target: {String.Join(", ", warningObjectList)}.", ValidationMessageType.Table, ValidationMessageStatus.Warning));
}
else
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to update table {comparisonObject.TargetObjectName} because version from the source depends on the following objects, which (considering changes) are missing from target and/or depend on a structured data source that is provider in the target: {String.Join(", ", warningObjectList)}.", ValidationMessageType.Table, ValidationMessageStatus.Warning));
}
}
}
}
#endregion
#region Relationships
private void DeleteRelationship(ComparisonObject comparisonObject)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Relationship && comparisonObject.MergeAction == MergeAction.Delete)
{
foreach (Table tableTarget in _targetTabularModel.Tables)
{
Relationship relationshipTarget = tableTarget.Relationships.FindByName(comparisonObject.TargetObjectName.Trim());
if (relationshipTarget != null)
{
// Relationship may have already been deleted if parent table was deleted
tableTarget.DeleteRelationship(comparisonObject.TargetObjectInternalName);
break;
}
}
OnValidationMessage(new ValidationMessageEventArgs($"Delete relationship {comparisonObject.TargetObjectName.Trim()}.", ValidationMessageType.Relationship, ValidationMessageStatus.Informational));
}
}
private void CreateRelationship(ComparisonObject comparisonObject, string tableName)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Relationship && comparisonObject.MergeAction == MergeAction.Create)
{
Table tableSource = _sourceTabularModel.Tables.FindByName(tableName);
Table tableTarget = _targetTabularModel.Tables.FindByName(tableName);
Relationship relationshipSource = tableSource.Relationships.FindByInternalName(comparisonObject.SourceObjectInternalName);
Table parentTableSource = _sourceTabularModel.Tables.FindByName(relationshipSource.ToTableName);
string warningMessage = $"Unable to create relationship {comparisonObject.SourceObjectName.Trim()} because (considering changes) necessary table/column(s) not found in target model.";
if (tableTarget != null && tableTarget.CreateRelationshipWithValidation(relationshipSource, parentTableSource.TomTable, comparisonObject.SourceObjectName.Trim(), ref warningMessage))
{
OnValidationMessage(new ValidationMessageEventArgs($"Create relationship {comparisonObject.SourceObjectName.Trim()}.", ValidationMessageType.Relationship, ValidationMessageStatus.Informational));
}
else
{
OnValidationMessage(new ValidationMessageEventArgs(warningMessage, ValidationMessageType.Relationship, ValidationMessageStatus.Warning));
}
}
}
private void UpdateRelationship(ComparisonObject comparisonObject, string tableName)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Relationship && comparisonObject.MergeAction == MergeAction.Update)
{
Table tableSource = _sourceTabularModel.Tables.FindByName(tableName);
Table tableTarget = _targetTabularModel.Tables.FindByName(tableName);
Relationship relationshipSource = tableSource.Relationships.FindByInternalName(comparisonObject.SourceObjectInternalName);
Table parentTableSource = _sourceTabularModel.Tables.FindByName(relationshipSource.ToTableName);
string warningMessage = "";
if (tableTarget.UpdateRelationship(relationshipSource, parentTableSource.TomTable, comparisonObject.SourceObjectName.Trim(), ref warningMessage))
{
OnValidationMessage(new ValidationMessageEventArgs($"Update relationship {comparisonObject.SourceObjectName.Trim()}.", ValidationMessageType.Relationship, ValidationMessageStatus.Informational));
}
else
{
OnValidationMessage(new ValidationMessageEventArgs(warningMessage, ValidationMessageType.Relationship, ValidationMessageStatus.Warning));
}
}
}
#endregion
#region Measures / KPIs
private void DeleteMeasure(ComparisonObject comparisonObject)
{
if ((comparisonObject.ComparisonObjectType == ComparisonObjectType.Measure || comparisonObject.ComparisonObjectType == ComparisonObjectType.Kpi) &&
comparisonObject.MergeAction == MergeAction.Delete)
{
foreach (Table tableTarget in _targetTabularModel.Tables)
{
Measure measureTarget = tableTarget.Measures.FindByName(comparisonObject.TargetObjectInternalName);
if (measureTarget != null)
{
// Measure may have already been deleted if parent table was deleted
tableTarget.DeleteMeasure(comparisonObject.TargetObjectInternalName);
break;
}
}
OnValidationMessage(new ValidationMessageEventArgs($"Delete measure / KPI {comparisonObject.TargetObjectInternalName}.", ValidationMessageType.Measure, ValidationMessageStatus.Informational));
}
}
private void CreateMeasure(ComparisonObject comparisonObject, string tableName)
{
if ((comparisonObject.ComparisonObjectType == ComparisonObjectType.Measure || comparisonObject.ComparisonObjectType == ComparisonObjectType.Kpi) &&
comparisonObject.MergeAction == MergeAction.Create)
{
foreach (Table tableInTarget in _targetTabularModel.Tables)
{
Measure measureInTarget = tableInTarget.Measures.FindByName(comparisonObject.SourceObjectInternalName);
if (measureInTarget != null)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to create measure / KPI {comparisonObject.SourceObjectInternalName} because name already exists in target model.", ValidationMessageType.Measure, ValidationMessageStatus.Warning));
return;
}
}
Table tableSource = _sourceTabularModel.Tables.FindByName(tableName);
Table tableTarget = _targetTabularModel.Tables.FindByName(tableName);
if (tableTarget == null)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to create measure / KPI {comparisonObject.SourceObjectInternalName} because (considering changes) target table does not exist.", ValidationMessageType.Measure, ValidationMessageStatus.Warning));
return;
}
//If we get here, can create measure/kpi
Measure measureSource = tableSource.Measures.FindByName(comparisonObject.SourceObjectInternalName);
tableTarget.CreateMeasure(measureSource.TomMeasure);
OnValidationMessage(new ValidationMessageEventArgs($"Create measure / KPI {comparisonObject.SourceObjectInternalName}.", ValidationMessageType.Measure, ValidationMessageStatus.Informational));
}
}
private void UpdateMeasure(ComparisonObject comparisonObject, string tableName)
{
if ((comparisonObject.ComparisonObjectType == ComparisonObjectType.Measure || comparisonObject.ComparisonObjectType == ComparisonObjectType.Kpi) &&
comparisonObject.MergeAction == MergeAction.Update)
{
Table tableSource = _sourceTabularModel.Tables.FindByName(tableName);
Table tableTarget = _targetTabularModel.Tables.FindByName(tableName);
Measure measureSource = tableSource.Measures.FindByName(comparisonObject.SourceObjectInternalName);
tableTarget.UpdateMeasure(measureSource.TomMeasure);
OnValidationMessage(new ValidationMessageEventArgs($"Update measure / KPI {comparisonObject.SourceObjectInternalName}.", ValidationMessageType.Measure, ValidationMessageStatus.Informational));
}
}
#endregion
#region CalculationItems
private void DeleteCalculationItem(ComparisonObject comparisonObject, string tableName)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.CalculationItem && comparisonObject.MergeAction == MergeAction.Delete)
{
Table tableTarget = _targetTabularModel.Tables.FindByName(tableName);
if (tableTarget != null)
{
CalculationItem calculationItemTarget = tableTarget.CalculationItems.FindByName(comparisonObject.TargetObjectInternalName);
if (calculationItemTarget != null)
{
// CalculationItem may have already been deleted if parent table was deleted
tableTarget.DeleteCalculationItem(comparisonObject.TargetObjectInternalName);
}
}
OnValidationMessage(new ValidationMessageEventArgs($"Delete calculation item {comparisonObject.TargetObjectInternalName}.", ValidationMessageType.CalculationItem, ValidationMessageStatus.Informational));
}
}
private void CreateCalculationItem(ComparisonObject comparisonObject, string tableName)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.CalculationItem && comparisonObject.MergeAction == MergeAction.Create)
{
Table tableSource = _sourceTabularModel.Tables.FindByName(tableName);
Table tableTarget = _targetTabularModel.Tables.FindByName(tableName);
if (tableTarget == null)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to create calculation item {comparisonObject.SourceObjectInternalName} because (considering changes) target table {tableName} does not exist.", ValidationMessageType.CalculationItem, ValidationMessageStatus.Warning));
return;
}
else if (!tableTarget.IsCalculationGroup)
{
OnValidationMessage(new ValidationMessageEventArgs($"Unable to create calculation item {comparisonObject.SourceObjectInternalName} because the target table {tableName} is not a calculation group table.", ValidationMessageType.CalculationItem, ValidationMessageStatus.Warning));
return;
}
//If we get here, can create calculationItem/kpi
CalculationItem calculationItemSource = tableSource.CalculationItems.FindByName(comparisonObject.SourceObjectInternalName);
tableTarget.CreateCalculationItem(calculationItemSource.TomCalculationItem);
OnValidationMessage(new ValidationMessageEventArgs($"Create calculation item {comparisonObject.SourceObjectInternalName}.", ValidationMessageType.CalculationItem, ValidationMessageStatus.Informational));
}
}
private void UpdateCalculationItem(ComparisonObject comparisonObject, string tableName)
{
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.CalculationItem && comparisonObject.MergeAction == MergeAction.Update)
{
Table tableSource = _sourceTabularModel.Tables.FindByName(tableName);
Table tableTarget = _targetTabularModel.Tables.FindByName(tableName);
CalculationItem calculationItemSource = tableSource.CalculationItems.FindByName(comparisonObject.SourceObjectInternalName);
tableTarget.UpdateCalculationItem(calculationItemSource.TomCalculationItem);
OnValidationMessage(new ValidationMessageEventArgs($"Update calculation item {comparisonObject.SourceObjectInternalName}.", ValidationMessageType.CalculationItem, ValidationMessageStatus.Informational));
}
}
#endregion
#endregion
private bool DesktopHardened(ComparisonObject comparisonObject, ValidationMessageType validationMessageType)
{
if (
(_targetTabularModel.ConnectionInfo.UseDesktop && _targetTabularModel.ConnectionInfo.ServerMode == Microsoft.AnalysisServices.ServerMode.SharePoint) ||
(_targetTabularModel.ConnectionInfo.UseBimFile && _targetTabularModel.ConnectionInfo.BimFile != null && _targetTabularModel.ConnectionInfo.IsPbit)
)
{
//V3 hardening
OnValidationMessage(new ValidationMessageEventArgs($"Unable to {comparisonObject.MergeAction.ToString().ToLower()} {comparisonObject.ComparisonObjectType.ToString()} {comparisonObject.TargetObjectName} because target is Power BI Desktop or .PBIT, which does not yet support modifications for this object type.", validationMessageType, ValidationMessageStatus.Warning));
return false;
}
else
{
return true;
}
}
/// <summary>
/// Update target tabular model with changes defined by actions in ComparisonObject instances.
/// </summary>
/// <returns>Flag to indicate whether update was successful.</returns>
public override bool Update() => _targetTabularModel.Update();
/// <summary>
/// Gets a collection of ProcessingTable objects depending on Process Affected Tables option.
/// </summary>
/// <returns>Collection of ProcessingTable objects.</returns>
public override ProcessingTableCollection GetTablesToProcess()
{
ProcessingTableCollection tablesToProcess = new ProcessingTableCollection();
if (_comparisonInfo.OptionsInfo.OptionProcessingOption != ProcessingOption.DoNotProcess)
{
if (_comparisonInfo.OptionsInfo.OptionAffectedTables)
{
foreach (Core.ComparisonObject comparisonObject in _comparisonObjects)
{
ProcessAffectedTables(comparisonObject, tablesToProcess);
}
}
else
{
foreach (Table table in _targetTabularModel.Tables)
{
tablesToProcess.Add(new ProcessingTable(table.Name, table.InternalName));
}
}
}
tablesToProcess.Sort();
return tablesToProcess;
}
private void ProcessAffectedTables(Core.ComparisonObject comparisonObject, ProcessingTableCollection tablesToProcess)
{
//Recursively call for multiple levels to ensure catch calculated tables or those child of DataSource
if (comparisonObject.ComparisonObjectType == ComparisonObjectType.Table &&
(comparisonObject.MergeAction == MergeAction.Create || comparisonObject.MergeAction == MergeAction.Update)
)
{
tablesToProcess.Add(new ProcessingTable(comparisonObject.SourceObjectName, comparisonObject.SourceObjectInternalName));
}
foreach (Core.ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects)
{
ProcessAffectedTables(childComparisonObject, tablesToProcess);
}
}
/// <summary>
/// Deploy database to target server and perform processing if required.
/// </summary>
/// <param name="tablesToProcess"></param>
public override void DatabaseDeployAndProcess(ProcessingTableCollection tablesToProcess)
{
_targetTabularModel.DatabaseDeployAndProcess(tablesToProcess);
}
/// <summary>
/// Stop processing of deployed database.
/// </summary>
public override void StopProcessing()
{
_targetTabularModel.StopProcessing();
}
/// <summary>
/// Generate script of target database including changes.
/// </summary>
/// <returns>Script.</returns>
public override string ScriptDatabase() => _targetTabularModel.ScriptDatabase();
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_sourceTabularModel != null)
{
_sourceTabularModel.Dispose();
}
if (_targetTabularModel != null)
{
_targetTabularModel.Dispose();
}
}
_disposed = true;
}
}
}
}