private void Init() { UndoManager = new UndoManager(this); Actions = new TabularCommonActions(this); Model = Model.CreateFromMetadata(database.Model); Model.Database = new Database(Model, database); //CheckErrors(); FormulaFixup.BuildDependencyTree(); }
protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue) { if (propertyName == Properties.EXPRESSION) { NeedsValidation = true; FormulaFixup.BuildDependencyTree(this); } base.OnPropertyChanged(propertyName, oldValue, newValue); }
protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue) { // Make sure only one column has the IsKey property set: if (propertyName == Properties.ISKEY && IsKey == true) { foreach (var c in Table.MetadataObject.Columns.Where(c => c.Type != TOM.ColumnType.RowNumber && c != this.MetadataObject)) { c.IsKey = false; } } // Make sure that Calculated Table Columns that originate from this column are updated to reflect name changes: if (propertyName == Properties.NAME) { // Fixup is not performed during an undo operation. We rely on the undo stack to fixup the expressions // affected by the name change (the undo stack should contain the expression changes that were made // when the name was initially changed). if (Handler.Settings.AutoFixup && !Handler.UndoManager.UndoInProgress) { FormulaFixup.DoFixup(this, true); } FormulaFixup.BuildDependencyTree(); foreach (var ctc in _originForCalculatedTableColumnsCache) { if (ctc.IsNameInferred) { ctc.Name = Name; ctc.IsNameInferred = true; } // This will inform the TOM what the origin of the new column is: ctc.SourceColumn = ctc.SourceColumn.Replace($"[{oldValue}]", $"[{newValue}]"); } // End the batch that was started in OnPropertyChanging: if (Handler.Settings.AutoFixup || _originForCalculatedTableColumnsCache.Count > 0) { Handler.EndUpdate(); } // Update relationship "names" if this column participates in any relationships: var rels = UsedInRelationships.ToList(); if (rels.Count > 1) { Handler.Tree.BeginUpdate(); } rels.ForEach(r => r.UpdateName()); if (rels.Count > 1) { Handler.Tree.EndUpdate(); } } base.OnPropertyChanged(propertyName, oldValue, newValue); }
protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue) { switch (propertyName) { case Properties.DETAILROWSEXPRESSION: case Properties.FORMATSTRINGEXPRESSION: case Properties.EXPRESSION: FormulaFixup.BuildDependencyTree(this); break; } base.OnPropertyChanged(propertyName, oldValue, newValue); }
protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue) { switch (propertyName) { case Properties.STATUSEXPRESSION: case Properties.TARGETEXPRESSION: case Properties.TRENDEXPRESSION: FormulaFixup.BuildDependencyTree(this); break; } base.OnPropertyChanged(propertyName, oldValue, newValue); }
protected override void SetValue(Table table, string filterExpression) { var tps = Role.MetadataObject.TablePermissions; var tp = tps.Find(table.Name); // Filter expression removed: if (string.IsNullOrWhiteSpace(filterExpression)) { // Don't do anything if there is no TablePermission for this table anyway: if (tp == null) { return; } // Otherwise, remove the TablePermission: tps.Remove(tp); Handler.UndoManager.Add(new UndoPropertyChangedAction(Role, "RowLevelSecurity", tp.FilterExpression, null, table.Name)); RLSFilterExpression rls; if (FilterExpressions.TryGetValue(table, out rls)) { FormulaFixup.ClearDependsOn(rls); _filterExpressions.Remove(table); } } else // Filter expression assigned: { // Create a new TablePermission if we don't already have one for this table: if (tp == null) { tp = new TOM.TablePermission() { Table = table.MetadataObject }; tps.Add(tp); } // Assign the new expression to the TablePermission: var oldValue = tp.FilterExpression; tp.FilterExpression = filterExpression; Role.Handler.UndoManager.Add(new UndoPropertyChangedAction(Role, "RowLevelSecurity", oldValue, filterExpression, table.Name)); if (!Handler.NameChangeInProgress) { var rls = RLSFilterExpression.Get(Role, table); FormulaFixup.BuildDependencyTree(rls); } } }
protected override void OnPropertyChanging(string propertyName, object newValue, ref bool undoable, ref bool cancel) { if (propertyName == Properties.NAME) { FormulaFixup.BuildDependencyTree(); // When formula fixup is enabled, we need to begin a new batch of undo operations, as this // name change could result in expression changes on multiple objects: if (Handler.Settings.AutoFixup) { Handler.UndoManager.BeginBatch("Set Property 'Name'"); } } base.OnPropertyChanging(propertyName, newValue, ref undoable, ref cancel); }
internal override void ReapplyReferences() { var container = this as ITabularObjectContainer; if (container != null) { foreach (var child in container.GetChildren().OfType <TabularObject>()) { child.ReapplyReferences(); } } if (this is IDaxDependantObject || this is IDaxObject || this is ModelRole) { FormulaFixup.BuildDependencyTree(); } }
protected override void OnPropertyChanging(string propertyName, object newValue, ref bool undoable, ref bool cancel) { if (propertyName == Properties.NAME) { FormulaFixup.BuildDependencyTree(); // When formula fixup is enabled, we need to begin a new batch of undo operations, as this // name change could result in expression changes on multiple objects. We also need to // start a new batch, in case this column is used as an origin for a calculated table column: _originForCalculatedTableColumnsCache = OriginForCalculatedTableColumns.ToList(); if (Handler.Settings.AutoFixup || _originForCalculatedTableColumnsCache.Count > 0) { Handler.UndoManager.BeginBatch("Set Property 'Name'"); } } base.OnPropertyChanging(propertyName, newValue, ref undoable, ref cancel); }
protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue) { if (propertyName == Properties.NAME) { if (Handler.Settings.AutoFixup) { FormulaFixup.DoFixup(this); Handler.UndoManager.EndBatch(); } Handler.Tree.FolderCache.Clear(); // Clear folder cache when a table is renamed. } if (propertyName == Properties.DEFAULTDETAILROWSEXPRESSION) { FormulaFixup.BuildDependencyTree(this); } base.OnPropertyChanged(propertyName, oldValue, newValue); }
protected override void OnPropertyChanging(string propertyName, object newValue, ref bool undoable, ref bool cancel) { if (propertyName == Properties.NAME) { // TODO: Important!!! // - Dependency Tree will be built once for every table that's had its name changed. This can be slow if many tables are renamed at once. // - We don't need a full rebuild of the dependency tree. We can limit ourselves to those expressions that contain a token matching the new name of this table. // - Also note that we should apply the fix-up before the tree is rebuilt. FormulaFixup.BuildDependencyTree(); // When formula fixup is enabled, we need to begin a new batch of undo operations, as this // name change could result in expression changes on multiple objects: if (Handler.Settings.AutoFixup) { Handler.UndoManager.BeginBatch("Set Property 'Name'"); } } base.OnPropertyChanging(propertyName, newValue, ref undoable, ref cancel); }
/// <summary> /// Ends all batch updates in progress. /// </summary> /// <param name="rollback"></param> /// <returns></returns> public int EndUpdateAll(bool rollback = false) { var actionCount = 0; while (UndoManager.BatchDepth > 0) { actionCount = UndoManager.EndBatch(rollback); } if (EoB_RequireRebuildDependencyTree) { EoB_PostponeOperations = false; FormulaFixup.BuildDependencyTree(); EoB_RequireRebuildDependencyTree = false; } while (Tree.UpdateLocks > 0) { Tree.EndUpdate(); } return(actionCount); }
protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue) { switch (propertyName) { case Properties.DETAILROWSEXPRESSION: case Properties.FORMATSTRINGEXPRESSION: case Properties.EXPRESSION: _needsValidation = true; FormulaFixup.BuildDependencyTree(this); break; case Properties.KPI: Handler.Tree.OnStructureChanged(this); break; case Properties.NAME: if (Handler.Settings.AutoFixup) { // Fixup is not performed during an undo operation. We rely on the undo stack to fixup the expressions // affected by the name change (the undo stack should contain the expression changes that were made // when the name was initially changed). if (!Handler.UndoManager.UndoInProgress) { FormulaFixup.DoFixup(this, true); } FormulaFixup.BuildDependencyTree(); Handler.EndUpdate(); // This batch was started in OnPropertyChanging } break; case Properties.FORMATSTRING: Handler.PowerBIGovernance.SuspendGovernance(); RemoveAnnotation("Format", true); Handler.PowerBIGovernance.ResumeGovernance(); Handler.EndUpdate(); break; } base.OnPropertyChanged(propertyName, oldValue, newValue); }
protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue) { if (propertyName == Properties.NAME) { if (Handler.Settings.AutoFixup) { // Fixup is not performed during an undo operation. We rely on the undo stack to fixup the expressions // affected by the name change (the undo stack should contain the expression changes that were made // when the name was initially changed). if (!Handler.UndoManager.UndoInProgress) { FormulaFixup.DoFixup(this); } Handler.EndUpdate(); } Handler.Tree.FolderCache.Clear(); // Clear folder cache when a table is renamed. // Update relationship "names" if this table participates in any relationships: var rels = UsedInRelationships.ToList(); if (rels.Count > 1) { Handler.Tree.BeginUpdate(); } rels.ForEach(r => r.UpdateName()); if (rels.Count > 1) { Handler.Tree.EndUpdate(); } } if (propertyName == Properties.DEFAULTDETAILROWSEXPRESSION) { FormulaFixup.BuildDependencyTree(this); } base.OnPropertyChanged(propertyName, oldValue, newValue); }
/// <summary> /// Inserts the specified list of objects into the model, at the optional destination. Objects that cannot /// be meaningfully inserted in the destination, will be inserted at the destination parent (recursively). /// If no suitable destination can be found, insertion will be ignored. /// Useful for drag-and-drop or copy-paste operations. /// </summary> /// <param name="objects"></param> /// <param name="destination"></param> public List <TabularObject> InsertObjects(ObjectJsonContainer objectContainer, ITabularNamedObject destination = null) { Handler.BeginUpdate("Paste objects"); var inserted = new List <TabularObject>(); // Possible destinations: var destHier = (destination as Level)?.Hierarchy ?? (destination as Hierarchy); var destTable = (destination as Folder)?.Table ?? (destination as Partition)?.Table ?? (destination as PartitionViewTable)?.Table ?? destHier?.Table ?? (destination as IDetailObject)?.Table ?? (destination as Table); var folder = (destination as Folder)?.Path; if (destHier != null) { // Levels can only be deserialized on a Hierarchy destination: foreach (var obj in objectContainer[typeof(Level)]) { inserted.Add(Serializer.DeserializeLevel(obj, destHier)); } destHier.CompactLevelOrdinals(); } if (destTable?.GetType() == typeof(Table)) { // DataColumns and Partitions can only be deserialized onto a Table destination (not CalculatedTable): foreach (var obj in objectContainer[typeof(DataColumn)]) { inserted.Add(Serializer.DeserializeDataColumn(obj, destTable)); } foreach (var obj in objectContainer[typeof(Partition)]) { inserted.Add(Serializer.DeserializePartition(obj, destTable)); } } if (destTable is Table) { // Measures, Hierarchies and CalculatedColumns can be deserialized onto a Table (or Table derived) destinated: foreach (var obj in objectContainer[typeof(CalculatedColumn)]) { inserted.Add(Serializer.DeserializeCalculatedColumn(obj, destTable)); } foreach (var obj in objectContainer[typeof(Hierarchy)]) { inserted.Add(Serializer.DeserializeHierarchy(obj, destTable)); } foreach (var obj in objectContainer[typeof(Measure)]) { inserted.Add(Serializer.DeserializeMeasure(obj, destTable)); } } foreach (var obj in objectContainer[typeof(CalculatedTable)]) { inserted.Add(Serializer.DeserializeCalculatedTable(obj, Handler.Model)); } foreach (var obj in objectContainer[typeof(Table)]) { inserted.Add(Serializer.DeserializeTable(obj, Handler.Model)); } foreach (var obj in objectContainer[typeof(ModelRole)]) { inserted.Add(Serializer.DeserializeModelRole(obj, Handler.Model)); } foreach (var obj in objectContainer[typeof(ProviderDataSource)]) { inserted.Add(Serializer.DeserializeProviderDataSource(obj, Handler.Model)); } foreach (var obj in objectContainer[typeof(SingleColumnRelationship)]) { inserted.Add(Serializer.DeserializeSingleColumnRelationship(obj, Handler.Model)); } foreach (var obj in objectContainer[typeof(Perspective)]) { inserted.Add(Serializer.DeserializePerspective(obj, Handler.Model)); } foreach (var obj in objectContainer[typeof(Culture)]) { inserted.Add(Serializer.DeserializeCulture(obj, Handler.Model)); } if (Handler.CompatibilityLevel >= 1400) { foreach (var obj in objectContainer[typeof(NamedExpression)]) { inserted.Add(Serializer.DeserializeNamedExpression(obj, Handler.Model)); } foreach (var obj in objectContainer[typeof(StructuredDataSource)]) { inserted.Add(Serializer.DeserializeStructuredDataSource(obj, Handler.Model)); } } foreach (var obj in inserted) { (obj as ITranslatableObject)?.LoadTranslations(true); (obj as ITabularPerspectiveObject)?.LoadPerspectives(true); (obj as Table)?.LoadRLS(); if (!string.IsNullOrEmpty(folder) && obj is IDetailObject) { (obj as IDetailObject).DisplayFolder = folder; } if (Handler.CompatibilityLevel >= 1400) { (obj as Table)?.LoadOLS(true); (obj as Column)?.LoadOLS(); } (obj as IAnnotationObject)?.ClearTabularEditorAnnotations(); } Handler.EndUpdate(); FormulaFixup.BuildDependencyTree(); return(inserted); }
/// <summary> /// Inserts the specified list of objects into the model, at the optional destination. Objects that cannot /// be meaningfully inserted in the destination, will be inserted at the destination parent (recursively). /// If no suitable destination can be found, insertion will be ignored. /// Useful for drag-and-drop or copy-paste operations. /// </summary> /// <param name="objects"></param> /// <param name="destination"></param> public List <TabularObject> InsertObjects(ObjectJsonContainer objectContainer, ITabularNamedObject destination = null) { // Possible destinations: var destHier = (destination as Level)?.Hierarchy ?? (destination as Hierarchy); var destTable = (destination as Folder)?.Table ?? (destination as Partition)?.Table ?? (destination as PartitionViewTable)?.Table ?? destHier?.Table ?? (destination as IFolderObject)?.Table ?? (destination as Table); var folder = (destination as Folder)?.Path; bool replaceTable = false; string replaceTableName = ""; JObject replaceTableJobj = null; bool replaceTableIsCalculated = false; // If the object container only holds a single object, and that object is a table, let's ask the user if they want to // replace the destination table with the table in the clipboard (unless of course it's the same table). if (destTable != null && objectContainer.Count == 1 && (objectContainer.Get <CalculatedTable>().Count() == 1 || objectContainer.Get <Table>().Count() == 1)) { replaceTableIsCalculated = objectContainer.Get <CalculatedTable>().Any(); replaceTableJobj = objectContainer.Get <CalculatedTable>().FirstOrDefault() ?? objectContainer.Get <Table>().FirstOrDefault(); replaceTableName = replaceTableJobj["name"].ToString(); // Check that the object came from a different instance, or that its another table: if (objectContainer.InstanceID != Handler.InstanceID || !destTable.Name.StartsWith(replaceTableName)) { var result = MessageBox.Show($"Do you want to replace table '{destTable.Name}' with table '{replaceTableName}' from the clipboard?\n\nExisting relationships will be kept, provided participating columns have the same name and data types in both the replaced and the inserted table.", "Replace existing table?", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information); if (result == DialogResult.Cancel) { return(new List <TabularObject>()); } if (result == DialogResult.Yes) { replaceTable = true; } } } Handler.BeginUpdate("Paste objects"); var inserted = new List <TabularObject>(); if (destHier != null) { // Levels can only be deserialized on a Hierarchy destination: foreach (var obj in objectContainer.Get <Level>()) { inserted.Add(Serializer.DeserializeLevel(obj, destHier)); } destHier.CompactLevelOrdinals(); } if (destTable?.GetType() == typeof(Table)) { // DataColumns and Partitions can only be deserialized onto a Table destination (not CalculatedTable): foreach (var obj in objectContainer.Get <DataColumn>()) { inserted.Add(Serializer.DeserializeDataColumn(obj, destTable)); } foreach (var obj in objectContainer.Get <Partition>()) { inserted.Add(Serializer.DeserializePartition(obj, destTable)); } } if (destTable is Table) { // Measures, Hierarchies and CalculatedColumns can be deserialized onto a Table (or Table derived) destinated: foreach (var obj in objectContainer.Get <CalculatedColumn>()) { inserted.Add(Serializer.DeserializeCalculatedColumn(obj, destTable)); } foreach (var obj in objectContainer.Get <Hierarchy>()) { inserted.Add(Serializer.DeserializeHierarchy(obj, destTable)); } foreach (var obj in objectContainer.Get <Measure>()) { inserted.Add(Serializer.DeserializeMeasure(obj, destTable)); } } // Replace an existing table with the one from the clipboard: if (replaceTable) { if (destTable.Name == replaceTableName) { // Similarly named tables - disable formula fixup: var fixupSetting = Handler.Settings.AutoFixup; Handler.Settings.AutoFixup = false; destTable.Name = destTable.Name + '-' + Guid.NewGuid().ToString(); Handler.Settings.AutoFixup = true; } else { // Differently named tables: // First, let's rename the destTable to match the new table (fix-up will handle DAX references): if (destTable.Name != replaceTableName) { destTable.Name = replaceTableName; } // Secondly, let's rename the table to be inserted (to avoid naming conflicts): replaceTableJobj["name"] = replaceTableName + '-' + Guid.NewGuid().ToString(); } // Insert the table: var newTable = replaceTableIsCalculated ? Serializer.DeserializeCalculatedTable(replaceTableJobj, Handler.Model) : Serializer.DeserializeTable(replaceTableJobj, Handler.Model); inserted.Add(newTable); // Update relationships to point to the inserted table: foreach (var rel in destTable.UsedInRelationships.ToList()) { if (rel.FromTable == destTable && newTable.Columns.Contains(rel.FromColumn.Name) && rel.FromColumn.DataType == newTable.Columns[rel.FromColumn.Name].DataType) { rel.FromColumn = newTable.Columns[rel.FromColumn.Name]; } if (rel.ToTable == destTable && newTable.Columns.Contains(rel.ToColumn.Name) && rel.ToColumn.DataType == newTable.Columns[rel.ToColumn.Name].DataType) { rel.ToColumn = newTable.Columns[rel.ToColumn.Name]; } } // Delete original table: destTable.Delete(); // Rename inserted table: newTable.Name = replaceTableName; } else { foreach (var obj in objectContainer.Get <CalculatedTable>()) { inserted.Add(Serializer.DeserializeCalculatedTable(obj, Handler.Model)); } foreach (var obj in objectContainer.Get <Table>()) { inserted.Add(Serializer.DeserializeTable(obj, Handler.Model)); } } foreach (var obj in objectContainer.Get <ModelRole>()) { inserted.Add(Serializer.DeserializeModelRole(obj, Handler.Model)); } foreach (var obj in objectContainer.Get <ProviderDataSource>()) { inserted.Add(Serializer.DeserializeProviderDataSource(obj, Handler.Model)); } foreach (var obj in objectContainer.Get <SingleColumnRelationship>()) { inserted.Add(Serializer.DeserializeSingleColumnRelationship(obj, Handler.Model)); } foreach (var obj in objectContainer.Get <Perspective>()) { inserted.Add(Serializer.DeserializePerspective(obj, Handler.Model)); } foreach (var obj in objectContainer.Get <Culture>()) { inserted.Add(Serializer.DeserializeCulture(obj, Handler.Model)); } if (Handler.CompatibilityLevel >= 1400) { foreach (var obj in objectContainer.Get <NamedExpression>()) { inserted.Add(Serializer.DeserializeNamedExpression(obj, Handler.Model)); } foreach (var obj in objectContainer.Get <StructuredDataSource>()) { inserted.Add(Serializer.DeserializeStructuredDataSource(obj, Handler.Model)); } } foreach (var obj in inserted) { (obj as ITranslatableObject)?.LoadTranslations(true); (obj as ITabularPerspectiveObject)?.LoadPerspectives(true); (obj as Table)?.LoadRLS(); if (!string.IsNullOrEmpty(folder) && obj is IFolderObject) { (obj as IFolderObject).DisplayFolder = folder; } if (Handler.CompatibilityLevel >= 1400) { (obj as Table)?.LoadOLS(true); (obj as Column)?.LoadOLS(); } (obj as IAnnotationObject)?.ClearTabularEditorAnnotations(); } Handler.EndUpdate(); FormulaFixup.BuildDependencyTree(); return(inserted); }