/// <summary> /// Merges the data. /// </summary> public void MergeData( ) { IProcessingContext context = Context; OldVersion.Setup(context); NewVersion.Setup(context); Target.Setup(context); context.WriteInfo("Loading package..."); _oldApp = AppContents.Load(OldVersion, context); context.WriteInfo("Loading tenant data..."); _newApp = AppContents.Load(NewVersion, context); context.WriteInfo("Processing entities..."); // Entities that the application explicitly does not want to delete ISet <Guid> doNotRemove = _newApp.DoNotRemove; ///// // Detect entity changes ///// List <EntityEntry> addedEntities, removedEntities, changedEntities, unchangedEntities; HashSet <Guid> removedEntitiesSet; long tenantId = -1; TenantMergeTarget tenantMergeTarget = Target as TenantMergeTarget; if (tenantMergeTarget != null) { tenantId = tenantMergeTarget.TenantId; } Guid isTenantDisabled = new Guid("11209a07-9189-4099-b609-80ed1d4f3e56"); Func <EntityEntry, bool> removeEntityAction = e => { if (tenantId == 0 && e.EntityId == isTenantDisabled) { EventLog.Application.WriteWarning("Attempt to delete the entity 'isTenantDisabled' from the global tenant has been prevented."); return(false); } return(true); }; Diff.DetectChanges(_oldApp.Entities, _newApp.Entities, null, null, removeEntityAction, null, null, out addedEntities, out removedEntities, out changedEntities, out unchangedEntities); // Suppress removal of entites that are flagged as 'do not remove' // removedEntities contains list of entities that we will actually remove removedEntities.RemoveAll(entityEntry => doNotRemove.Contains(entityEntry.EntityId)); // Capture raw set of entities that are being removed (excluding the doNotRemove ones) removedEntitiesSet = new HashSet <Guid>(removedEntities.Select(entityEntry => entityEntry.EntityId)); Context.Report.AddedEntities = addedEntities; Context.Report.RemovedEntities = removedEntities; Context.Report.UpdatedEntities = changedEntities; if (_oldApp.Entities != null) { Context.Report.Counts.Add(new StatisticsCount("Previous Application Entities", _oldApp.Entities.Count, StatisticsCountType.PreviousApplication)); } if (_newApp.Entities != null) { Context.Report.Counts.Add(new StatisticsCount("Current Application Entities", _newApp.Entities.Count, StatisticsCountType.CurrentApplication)); } Context.Report.Counts.Add(new StatisticsCount("Added Entities", addedEntities.Count, StatisticsCountType.Added)); Context.Report.Counts.Add(new StatisticsCount("Removed Entities", removedEntities.Count, StatisticsCountType.Removed)); Context.Report.Counts.Add(new StatisticsCount("Updated Entities", changedEntities.Count, StatisticsCountType.Updated)); Context.Report.Counts.Add(new StatisticsCount("Unchanged Entities", unchangedEntities.Count, StatisticsCountType.Unchanged)); ///// // Apply entity changes // note: 'changedEntities' is always empty ///// Target.WriteEntities(addedEntities, context); context.WriteInfo("Processing relationships..."); ///// // Detect relationship changes ///// List <RelationshipEntry> addedRelationships, removedRelationships, changedRelationships, unchangedRelationships; var changeAction = new Func <RelationshipEntry, RelationshipEntry, bool>((o, n) => { if (n.Cardinality == CardinalityEnum_Enumeration.ManyToOne) { n.PreviousValue = o.ToId; return(true); } if (n.Cardinality == CardinalityEnum_Enumeration.OneToMany) { n.PreviousValue = o.FromId; return(true); } if (n.Cardinality == CardinalityEnum_Enumeration.OneToOne) { if (o.FromId == n.FromId) { n.PreviousValue = o.ToId; n.UpdateTo = true; n.UpdateFrom = false; } else { n.PreviousValue = o.FromId; n.UpdateTo = false; n.UpdateFrom = true; } } return(true); }); Func <RelationshipEntry, bool> removeRelationshipAction = e => { if (tenantId == 0) { if (e.FromId == isTenantDisabled) { EventLog.Application.WriteWarning("Attempt to delete relatiosnhip from 'isTenantDisabled' in the global tenant has been prevented."); return(false); } if (e.ToId == isTenantDisabled) { EventLog.Application.WriteWarning("Attempt to delete relationship to 'isTenantDisabled' in the global tenant has been prevented."); return(false); } } return(true); }; Diff.DetectChanges(_oldApp.Relationships, _newApp.Relationships, _oldApp.MissingRelationships, null, removeRelationshipAction, changeAction, null, out addedRelationships, out removedRelationships, out changedRelationships, out unchangedRelationships); // Don't remove relationship content for entities that are flagged as 'do not delete' (unless the other end is being deleted) removedRelationships.RemoveAll(relationshipEntry => { if (!(doNotRemove.Contains(relationshipEntry.ToId) || doNotRemove.Contains(relationshipEntry.FromId))) { return(false); // neither end is in the 'do not remove' list, so just carry on with the deletion (by leaving the row in the removal collection) } // Always allow apps to remove their association to an entity if (relationshipEntry.TypeId == Guids.InSolution || relationshipEntry.TypeId == Guids.IndirectInSolution) { return(false); } // If the other end of the relationship (or the relationship type) is being deleted, then delete the relationship instance anyway if (removedEntitiesSet.Contains(relationshipEntry.ToId)) { return(false); } if (removedEntitiesSet.Contains(relationshipEntry.FromId)) { return(false); } if (removedEntitiesSet.Contains(relationshipEntry.TypeId)) { return(false); } return(true); // the relationship is partly in the do-not-remove list, and the other end is not being removed, so don't delete this relationship }); Context.Report.AddedRelationships = addedRelationships; Context.Report.RemovedRelationships = removedRelationships; Context.Report.UpdatedRelationships = changedRelationships; if (_oldApp.Relationships != null) { Context.Report.Counts.Add(new StatisticsCount("Previous Application Relationships", _oldApp.Relationships.Count, StatisticsCountType.PreviousApplication)); } if (_newApp.Relationships != null) { Context.Report.Counts.Add(new StatisticsCount("Current Application Relationships", _newApp.Relationships.Count, StatisticsCountType.CurrentApplication)); } Context.Report.Counts.Add(new StatisticsCount("Added Relationships", addedRelationships.Count, StatisticsCountType.Added)); Context.Report.Counts.Add(new StatisticsCount("Removed Relationships", removedRelationships.Count, StatisticsCountType.Removed)); Context.Report.Counts.Add(new StatisticsCount("Updated Relationships", changedRelationships.Count, StatisticsCountType.Updated)); Context.Report.Counts.Add(new StatisticsCount("Unchanged Relationships", unchangedRelationships.Count, StatisticsCountType.Unchanged)); ///// // Apply relationship changes ///// Target.UpdateRelationships(changedRelationships, context); Target.WriteRelationships(addedRelationships, context); Target.PreDeleteEntities(removedEntities, context); Target.DeleteRelationships(removedRelationships, context); Target.DeleteEntities(removedEntities, context); ///// // Invalidate all per-tenant caches at this point. // This is to ensure caches that are holding onto type information are flushed. ///// InvalidatePerTenantCaches(tenantId); Func <DataEntry, bool> removedAction = de => { if (de != null) { if (tenantId == 0 && de.EntityId == isTenantDisabled) { EventLog.Application.WriteWarning("Attempt to delete field on entity 'isTenantDisabled' in the global tenant has been prevented."); return(false); } } return(true); }; Func <DataEntry, DataEntry, bool> changedAction = (oldVal, newVal) => { newVal.ExistingData = oldVal.Data; return(true); }; ///// // Detect and apply data changes ///// foreach (string dataTable in Helpers.FieldDataTables) { context.WriteInfo($"Processing '{dataTable}'..."); List <DataEntry> addedData, removedData, changedData, unchangedData; Dictionary <Tuple <Guid, Guid>, DataEntry> oldData = _oldApp.FieldData[dataTable]; Dictionary <Tuple <Guid, Guid>, DataEntry> missingData = _oldApp.MissingFieldData; Dictionary <Tuple <Guid, Guid>, DataEntry> newData = _newApp.FieldData[dataTable]; Diff.DetectChanges(oldData, newData, missingData, null, removedAction, changedAction, null, out addedData, out removedData, out changedData, out unchangedData); // Don't remove content for entities that are tagged as 'do not delete' removedData.RemoveAll(dataEntry => _newApp.DoNotRemove.Contains(dataEntry.EntityId)); context.Report.AddedEntityData[dataTable] = addedData; context.Report.RemovedEntityData[dataTable] = removedData; context.Report.UpdatedEntityData[dataTable] = changedData; if (oldData != null) { Context.Report.Counts.Add(new StatisticsCount($"Previous Application {dataTable} Data", oldData.Count, StatisticsCountType.PreviousApplication)); } if (newData != null) { Context.Report.Counts.Add(new StatisticsCount($"Current Application {dataTable} Data", newData.Count, StatisticsCountType.CurrentApplication)); } Context.Report.Counts.Add(new StatisticsCount($"Added {dataTable} Data", addedData.Count, StatisticsCountType.Added)); Context.Report.Counts.Add(new StatisticsCount($"Removed {dataTable} Data", removedData.Count, StatisticsCountType.Removed)); Context.Report.Counts.Add(new StatisticsCount($"Updated {dataTable} Data", changedData.Count, StatisticsCountType.Updated)); Context.Report.Counts.Add(new StatisticsCount($"Unchanged {dataTable} Data", unchangedData.Count, StatisticsCountType.Unchanged)); Target.WriteFieldData(dataTable, addedData, context); Target.DeleteFieldData(dataTable, removedData, context); Target.UpdateFieldData(dataTable, changedData, context); } context.WriteInfo("Processing binary data..."); ///// // Detect binary data changes ///// List <BinaryDataEntry> addedbinaryData, removedBinaryData, changedBinaryData, unchangedBinaryData; Diff.DetectChanges(_oldApp.BinaryData, _newApp.BinaryData, null, null, null, null, null, out addedbinaryData, out removedBinaryData, out changedBinaryData, out unchangedBinaryData); context.Report.AddedBinaryData = addedbinaryData; context.Report.RemovedBinaryData = removedBinaryData; context.Report.UpdatedBinaryData = changedBinaryData; if (_oldApp.BinaryData != null) { Context.Report.Counts.Add(new StatisticsCount("Previous Application Binary Data", _oldApp.BinaryData.Count, StatisticsCountType.PreviousApplication)); } if (_newApp.BinaryData != null) { Context.Report.Counts.Add(new StatisticsCount("Current Application Binary Data", _newApp.BinaryData.Count, StatisticsCountType.CurrentApplication)); } Context.Report.Counts.Add(new StatisticsCount("Added Binary Data", addedbinaryData.Count, StatisticsCountType.Added)); Context.Report.Counts.Add(new StatisticsCount("Removed Binary Data", removedBinaryData.Count, StatisticsCountType.Removed)); Context.Report.Counts.Add(new StatisticsCount("Updated Binary Data", changedBinaryData.Count, StatisticsCountType.Updated)); Context.Report.Counts.Add(new StatisticsCount("Unchanged Binary Data", unchangedBinaryData.Count, StatisticsCountType.Unchanged)); ///// // Apply binary changes ///// Target.WriteBinaryData(addedbinaryData, context); // Update before delete. This is to ensure that if any files have the IsReferencedExternally flag set // that they will not be deleted. Target.UpdateBinaryData(changedBinaryData, context); Target.DeleteBinaryData(removedBinaryData, context); context.WriteInfo("Processing document data..."); ///// // Detect binary data changes ///// List <DocumentDataEntry> addedDocumentData, removedDocumentData, changedDocumentData, unchangedDocumentData; Diff.DetectChanges(_oldApp.DocumentData, _newApp.DocumentData, null, null, null, null, null, out addedDocumentData, out removedDocumentData, out changedDocumentData, out unchangedDocumentData); context.Report.AddedDocumentData = addedDocumentData; context.Report.RemovedDocumentData = removedDocumentData; context.Report.UpdatedDocumentData = changedDocumentData; if (_oldApp.DocumentData != null) { Context.Report.Counts.Add(new StatisticsCount("Previous Application Document Data", _oldApp.DocumentData.Count, StatisticsCountType.PreviousApplication)); } if (_newApp.DocumentData != null) { Context.Report.Counts.Add(new StatisticsCount("Current Application Document Data", _newApp.DocumentData.Count, StatisticsCountType.CurrentApplication)); } Context.Report.Counts.Add(new StatisticsCount("Added Document Data", addedDocumentData.Count, StatisticsCountType.Added)); Context.Report.Counts.Add(new StatisticsCount("Removed Document Data", removedDocumentData.Count, StatisticsCountType.Removed)); Context.Report.Counts.Add(new StatisticsCount("Updated Document Data", changedDocumentData.Count, StatisticsCountType.Updated)); Context.Report.Counts.Add(new StatisticsCount("Unchanged Document Data", unchangedDocumentData.Count, StatisticsCountType.Unchanged)); ///// // Apply binary changes ///// Target.WriteDocumentData(addedDocumentData, context); Target.UpdateDocumentData(changedDocumentData, context); Target.DeleteDocumentData(removedDocumentData, context); Target.TearDown(context); NewVersion.TearDown(context); OldVersion.TearDown(context); }
/// <summary> /// Stages the data. /// </summary> public void StageData( ) { IProcessingContext context = Context; NewVersion.Setup(context); OldVersion.Setup(context); _oldApp = AppContents.Load(OldVersion, context, true); _newApp = AppContents.Load(NewVersion, context); ///// // Detect entity changes ///// List <EntityEntry> addedEntities, removedEntities, changedEntities, unchangedEntities; Diff.DetectChanges(_oldApp.Entities, _newApp.Entities, null, AddAction, RemoveAction, UpdateAction, UnchangedAction, out addedEntities, out removedEntities, out changedEntities, out unchangedEntities); Context.Report.AddedEntities = addedEntities; Context.Report.RemovedEntities = removedEntities; Context.Report.UpdatedEntities = changedEntities; Context.Report.UnchangedEntities = unchangedEntities; if (_oldApp.Entities != null) { Context.Report.Counts.Add(new StatisticsCount("Previous Application Entities", _oldApp.Entities.Count, StatisticsCountType.PreviousApplication)); } if (_newApp.Entities != null) { Context.Report.Counts.Add(new StatisticsCount("Current Application Entities", _newApp.Entities.Count, StatisticsCountType.CurrentApplication)); } Context.Report.Counts.Add(new StatisticsCount("Added Entities", addedEntities.Count, StatisticsCountType.Added)); Context.Report.Counts.Add(new StatisticsCount("Removed Entities", removedEntities.Count, StatisticsCountType.Removed)); Context.Report.Counts.Add(new StatisticsCount("Updated Entities", changedEntities.Count, StatisticsCountType.Updated)); Context.Report.Counts.Add(new StatisticsCount("Unchanged Entities", unchangedEntities.Count, StatisticsCountType.Unchanged)); ///// // Detect relationship changes ///// List <RelationshipEntry> addedRelationships, removedRelationships, changedRelationships, unchangedRelationships; Diff.DetectChanges(_oldApp.Relationships, _newApp.Relationships, null, null, null, null, null, out addedRelationships, out removedRelationships, out changedRelationships, out unchangedRelationships); Context.Report.AddedRelationships = addedRelationships; Context.Report.RemovedRelationships = removedRelationships; Context.Report.UpdatedRelationships = changedRelationships; if (_oldApp.Relationships != null) { Context.Report.Counts.Add(new StatisticsCount("Previous Application Relationships", _oldApp.Relationships.Count, StatisticsCountType.PreviousApplication)); } if (_newApp.Relationships != null) { Context.Report.Counts.Add(new StatisticsCount("Current Application Relationships", _newApp.Relationships.Count, StatisticsCountType.CurrentApplication)); } Context.Report.Counts.Add(new StatisticsCount("Added Relationships", addedRelationships.Count, StatisticsCountType.Added)); Context.Report.Counts.Add(new StatisticsCount("Removed Relationships", removedRelationships.Count, StatisticsCountType.Removed)); Context.Report.Counts.Add(new StatisticsCount("Updated Relationships", changedRelationships.Count, StatisticsCountType.Updated)); Context.Report.Counts.Add(new StatisticsCount("Unchanged Relationships", unchangedRelationships.Count, StatisticsCountType.Unchanged)); Func <DataEntry, DataEntry, bool> changedAction = (oldVal, newVal) => { newVal.ExistingData = oldVal.Data; return(true); }; ///// // Detect and apply data changes ///// foreach (string dataTable in Helpers.FieldDataTables) { List <DataEntry> addedData, removedData, changedData, unchangedData; Dictionary <Tuple <Guid, Guid>, DataEntry> oldData = _oldApp.FieldData[dataTable]; Dictionary <Tuple <Guid, Guid>, DataEntry> newData = _newApp.FieldData[dataTable]; Diff.DetectChanges(oldData, newData, null, null, null, changedAction, null, out addedData, out removedData, out changedData, out unchangedData); context.Report.AddedEntityData[dataTable] = addedData; context.Report.RemovedEntityData[dataTable] = removedData; context.Report.UpdatedEntityData[dataTable] = changedData; if (oldData != null) { Context.Report.Counts.Add(new StatisticsCount($"Previous Application {dataTable} Data", oldData.Count, StatisticsCountType.PreviousApplication)); } if (newData != null) { Context.Report.Counts.Add(new StatisticsCount($"Current Application {dataTable} Data", newData.Count, StatisticsCountType.CurrentApplication)); } Context.Report.Counts.Add(new StatisticsCount($"Added {dataTable} Data", addedData.Count, StatisticsCountType.Added)); Context.Report.Counts.Add(new StatisticsCount($"Removed {dataTable} Data", removedData.Count, StatisticsCountType.Removed)); Context.Report.Counts.Add(new StatisticsCount($"Updated {dataTable} Data", changedData.Count, StatisticsCountType.Updated)); Context.Report.Counts.Add(new StatisticsCount($"Unchanged {dataTable} Data", unchangedData.Count, StatisticsCountType.Unchanged)); } ///// // Detect binary data changes ///// List <BinaryDataEntry> addedbinaryData, removedBinaryData, changedBinaryData, unchangedBinaryData; Diff.DetectChanges(_oldApp.BinaryData, _newApp.BinaryData, null, null, null, null, null, out addedbinaryData, out removedBinaryData, out changedBinaryData, out unchangedBinaryData); context.Report.AddedBinaryData = addedbinaryData; context.Report.RemovedBinaryData = removedBinaryData; context.Report.UpdatedBinaryData = changedBinaryData; if (_oldApp.BinaryData != null) { Context.Report.Counts.Add(new StatisticsCount("Previous Application Binary Data", _oldApp.BinaryData.Count, StatisticsCountType.PreviousApplication)); } if (_newApp.BinaryData != null) { Context.Report.Counts.Add(new StatisticsCount("Current Application Binary Data", _newApp.BinaryData.Count, StatisticsCountType.CurrentApplication)); } Context.Report.Counts.Add(new StatisticsCount("Added Binary Data", addedbinaryData.Count, StatisticsCountType.Added)); Context.Report.Counts.Add(new StatisticsCount("Removed Binary Data", removedBinaryData.Count, StatisticsCountType.Removed)); Context.Report.Counts.Add(new StatisticsCount("Updated Binary Data", changedBinaryData.Count, StatisticsCountType.Updated)); Context.Report.Counts.Add(new StatisticsCount("Unchanged Binary Data", unchangedBinaryData.Count, StatisticsCountType.Unchanged)); ///// // Detect binary data changes ///// List <DocumentDataEntry> addedDocumentData, removedDocumentData, changedDocumentData, unchangedDocumentData; Diff.DetectChanges(_oldApp.DocumentData, _newApp.DocumentData, null, null, null, null, null, out addedDocumentData, out removedDocumentData, out changedDocumentData, out unchangedDocumentData); context.Report.AddedDocumentData = addedDocumentData; context.Report.RemovedDocumentData = removedDocumentData; context.Report.UpdatedDocumentData = changedDocumentData; if (_oldApp.DocumentData != null) { Context.Report.Counts.Add(new StatisticsCount("Previous Application Document Data", _oldApp.DocumentData.Count, StatisticsCountType.PreviousApplication)); } if (_newApp.DocumentData != null) { Context.Report.Counts.Add(new StatisticsCount("Current Application Document Data", _newApp.DocumentData.Count, StatisticsCountType.CurrentApplication)); } Context.Report.Counts.Add(new StatisticsCount("Added Document Data", addedDocumentData.Count, StatisticsCountType.Added)); Context.Report.Counts.Add(new StatisticsCount("Removed Document Data", removedDocumentData.Count, StatisticsCountType.Removed)); Context.Report.Counts.Add(new StatisticsCount("Updated Document Data", changedDocumentData.Count, StatisticsCountType.Updated)); Context.Report.Counts.Add(new StatisticsCount("Unchanged Document Data", unchangedDocumentData.Count, StatisticsCountType.Unchanged)); OldVersion.TearDown(context); NewVersion.TearDown(context); }