Example #1
0
        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.

                // 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>
        /// Ends the latest batch update (can never be called more times than BeginUpdate).
        /// </summary>
        /// <param name="undoable"></param>
        /// <param name="rollback"></param>
        /// <returns></returns>
        public int EndUpdate(bool undoable = true, bool rollback = false)
        {
            if (_disableUpdates)
            {
                return(0);
            }

            var actionCount = 0;

            if (undoable || rollback)
            {
                actionCount = UndoManager.EndBatch(rollback);
            }

            if (Tree.UpdateLocks == 1)
            {
                EoB_PostponeOperations = false;
            }
            if (Tree.UpdateLocks == 1 && EoB_RequireRebuildDependencyTree)
            {
                FormulaFixup.BuildDependencyTree();
                EoB_RequireRebuildDependencyTree = false;
            }

            // This takes care of reducing the UpdateLocks counter, and notifying the UI when we reach zero:
            Tree.EndUpdate();

            return(actionCount);
        }
Example #3
0
        protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue)
        {
            switch (propertyName)
            {
            case Properties.DETAILROWSEXPRESSION:
            case Properties.EXPRESSION:
                NeedsValidation = true;
                FormulaFixup.BuildDependencyTree(this);
                break;

            case Properties.KPI:
                Handler.Tree.OnStructureChanged(this);
                break;

            case Properties.NAME:
                if (Handler.Settings.AutoFixup)
                {
                    FormulaFixup.DoFixup(this);
                    Handler.UndoManager.EndBatch();     // This batch was started in OnPropertyChanging
                }
                break;
            }

            base.OnPropertyChanged(propertyName, oldValue, newValue);
        }
Example #4
0
        protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue)
        {
            switch (propertyName)
            {
            case Properties.DETAILROWSEXPRESSION:
            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;
            }

            base.OnPropertyChanged(propertyName, oldValue, newValue);
        }
 /// <summary>
 /// Triggers when a batch of operations has completed.
 /// If any end-of-batch flags were set during the batch, these will be triggered now
 /// and the flags will be reset.
 /// </summary>
 private void DoUpdateComplete()
 {
     if (EoB_BuildDependencyTree)
     {
         FormulaFixup.BuildDependencyTree(); EoB_BuildDependencyTree = false;
     }
 }
        /// <summary>
        /// Ends the latest batch update (can never be called more times than BeginUpdate).
        /// </summary>
        /// <param name="undoable"></param>
        /// <param name="rollback"></param>
        /// <returns></returns>
        public int EndUpdate(bool undoable = true, bool rollback = false)
        {
            if (_disableUpdates)
            {
                return(0);
            }

            var actionCount = 0;

            if (undoable || rollback)
            {
                actionCount = UndoManager.EndBatch(rollback);
            }
            Tree.EndUpdate();

            if (!InsideTransaction)
            {
                DoUpdateComplete();
            }

            if (NameChangeInProgress && Tree.UpdateLocks == 0)
            {
                NameChangeInProgress = false;
                if (Settings.AutoFixup)
                {
                    FormulaFixup.BuildDependencyTree();
                }
            }

            return(actionCount);
        }
Example #7
0
        protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue)
        {
            if (propertyName == Properties.EXPRESSION)
            {
                FormulaFixup.BuildDependencyTree(this);
            }

            base.OnPropertyChanged(propertyName, oldValue, newValue);
        }
        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();
        }
Example #9
0
        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);
        }
Example #10
0
        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)
            {
                // Do formula fixup if enabled:
                if (Handler.Settings.AutoFixup)
                {
                    FormulaFixup.DoFixup(this);
                }

                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.UndoManager.EndBatch();
                }

                // 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);
        }
Example #11
0
        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);
        }
Example #12
0
        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);
        }
Example #13
0
        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);
                }
            }
        }
Example #14
0
        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);
        }
Example #15
0
        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);
        }
Example #16
0
        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();
            }
        }
Example #17
0
        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);
        }
Example #18
0
        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);
        }
Example #20
0
        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)
        {
            // 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);
        }
        /// <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);
        }