/// <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);
        }