/// <summary> /// Updates the document data. /// </summary> /// <param name="documentData">The document data.</param> /// <param name="context">The context.</param> public void UpdateDocumentData(IEnumerable <DocumentDataEntry> documentData, IProcessingContext context) { foreach (DocumentDataEntry documentDataEntry in documentData) { context.WriteInfo(string.Format("Updated binary data: {0}", documentDataEntry.DataHash)); } }
/// <summary> /// Setups the specified context. /// </summary> /// <param name="context">The context.</param> void IDataTarget.Setup(IProcessingContext context) { context?.WriteInfo("Initializing..."); long userId; RequestContext.TryGetUserId(out userId); using (EntryPointContext.UnsafeToIncludeEntryPoint( )) using (IDbCommand command = CreateCommand( )) { command.CommandText = CommandText.TenantRepairTargetSetupCommandText; command.CommandType = CommandType.Text; command.ExecuteNonQuery( ); command.CommandText = @" IF ( @context IS NOT NULL ) BEGIN DECLARE @contextInfo VARBINARY(128) = CONVERT( VARBINARY(128), @context ) SET CONTEXT_INFO @contextInfo END"; command.AddParameter("@context", DbType.AnsiString, DatabaseContextInfo.GetMessageChain(userId), 128); command.ExecuteNonQuery( ); } }
/// <summary> /// Deletes field data. /// </summary> /// <param name="dataTable"></param> /// <param name="data"></param> /// <param name="context"></param> void IMergeTarget.DeleteFieldData(string dataTable, IEnumerable <DataEntry> data, IProcessingContext context) { foreach (DataEntry entry in data) { context.WriteInfo(string.Format("Deleted data: {0} {1}", entry.EntityId, entry.FieldId)); } }
/// <summary> /// Migrates the data. /// </summary> public void MigrateData( ) { IProcessingContext context = Context; ///// // Run any setup logic. ///// DataSource.Setup(context); DataTarget.Setup(context); ///// // Migrate metadata ///// context.WriteInfo("Copying metadata..."); Metadata metadata = DataSource.GetMetadata(context); DataTarget.SetMetadata(metadata, context); if (!CopyMetadataOnly) { MigrateContent(context); } ///// // Run any teardown logic. ///// DataTarget.TearDown(context); DataSource.TearDown(context); }
/// <summary> /// Deletes relationships. /// </summary> /// <param name="relationships"></param> /// <param name="context"></param> void IMergeTarget.DeleteRelationships(IEnumerable <RelationshipEntry> relationships, IProcessingContext context) { foreach (RelationshipEntry rel in relationships) { context.WriteInfo(string.Format("Deleted relationship: {0} {1} {2}", rel.TypeId, rel.FromId, rel.ToId)); } }
/// <summary> /// Deletes the document data. /// </summary> /// <param name="documentData">The document data.</param> /// <param name="context">The context.</param> public void DeleteDocumentData(IEnumerable <DocumentDataEntry> documentData, IProcessingContext context) { foreach (DocumentDataEntry documentDataEntry in documentData) { context.WriteInfo("Deleted document data: " + documentDataEntry.DataHash); } }
/// <summary> /// Deletes entities. /// </summary> /// <param name="entities"></param> /// <param name="context"></param> void IMergeTarget.DeleteEntities(IEnumerable <EntityEntry> entities, IProcessingContext context) { foreach (EntityEntry entity in entities) { context.WriteInfo("Deleted entity: " + entity.EntityId); } }
/// <summary> /// Write in collection of relationships. /// </summary> /// <param name="relationships"></param> /// <param name="context"></param> void IDataTarget.WriteSecureData(IEnumerable <SecureDataEntry> data, IProcessingContext context) { foreach (var entry in data) { context.WriteInfo($"Added secure entry: {entry.SecureId} {entry.Context} Length:{entry.Data.Length}"); } }
/// <summary> /// Write list of entities that should not be removed during upgrade operations. /// </summary> /// <param name="entities"></param> /// <param name="context"></param> void IDataTarget.WriteDoNotRemove(IEnumerable <Guid> entities, IProcessingContext context) { foreach (Guid entity in entities) { context.WriteInfo("Do not remove: " + entity); } }
public void WriteDocumentData(IEnumerable <DocumentDataEntry> data, IProcessingContext context) { foreach (DocumentDataEntry dataEntry in data) { context.WriteInfo(string.Format("Added Document DataHash:{0}", dataEntry.DataHash)); } }
/// <summary> /// Deletes binary data entries. /// </summary> /// <param name="binaryData"></param> /// <param name="context"></param> void IMergeTarget.DeleteBinaryData(IEnumerable <BinaryDataEntry> binaryData, IProcessingContext context) { foreach (BinaryDataEntry binaryDataEntry in binaryData) { context.WriteInfo("Deleted binary data: " + binaryDataEntry.DataHash); } }
/// <summary> /// Writes the binary data. /// </summary> /// <param name="data">The data.</param> /// <param name="context">The context.</param> void IDataTarget.WriteBinaryData(IEnumerable <BinaryDataEntry> data, IProcessingContext context) { foreach (BinaryDataEntry dataEntry in data) { context.WriteInfo(string.Format("Added Binary DataHash:{0}", dataEntry.DataHash)); } }
/// <summary> /// Write in collection of relationships. /// </summary> void IDataTarget.WriteRelationships(IEnumerable <RelationshipEntry> relationships, IProcessingContext context) { using (IDbCommand command = CreateCommand( )) { const string sql = "insert into _Relationship (FromUid, ToUid, TypeUid, EntityUid) values (@from, @to, @type, @entity)"; command.CommandText = sql; command.Parameters.Add(new SQLiteParameter("@from", DbType.String)); command.Parameters.Add(new SQLiteParameter("@to", DbType.String)); command.Parameters.Add(new SQLiteParameter("@type", DbType.String)); command.Parameters.Add(new SQLiteParameter("@entity", DbType.String)); int rowCount = 0; foreach (RelationshipEntry relationship in relationships) { (( SQLiteParameter )command.Parameters[0]).Value = relationship.FromId; (( SQLiteParameter )command.Parameters[1]).Value = relationship.ToId; (( SQLiteParameter )command.Parameters[2]).Value = relationship.TypeId; rowCount += command.ExecuteNonQuery( ); if (rowCount % 100 == 0) { context.WriteInfo($"Copying Relationship data... {rowCount} rows"); } } } }
/// <summary> /// Called for binary data with new values. /// </summary> /// <param name="data"></param> /// <param name="context"></param> void IMergeTarget.UpdateBinaryData(IEnumerable <BinaryDataEntry> data, IProcessingContext context) { foreach (BinaryDataEntry entry in data) { context.WriteInfo(string.Format("Updated binary data: {0}", entry.DataHash)); } }
/// <summary> /// Set the application metadata. /// </summary> /// <param name="metadata"></param> /// <param name="context"></param> void IDataTarget.SetMetadata(Metadata metadata, IProcessingContext context) { context.WriteInfo("Name: " + metadata.Name); context.WriteInfo("Description: " + metadata.Description); context.WriteInfo("Version: " + metadata.Version); context.WriteInfo("Publisher: " + metadata.Publisher); context.WriteInfo("PublisherUrl: " + metadata.PublisherUrl); context.WriteInfo("ReleaseDate: " + metadata.ReleaseDate); context.WriteInfo("AppId: " + metadata.AppId); context.WriteInfo("AppVerId: " + metadata.AppVerId); }
/// <summary> /// Tears down. /// </summary> /// <param name="context">The context.</param> void IDataTarget.TearDown(IProcessingContext context) { context?.WriteInfo("Finalizing..."); using (EntryPointContext.UnsafeToIncludeEntryPoint( )) using (IDbCommand command = CreateCommand( )) { command.CommandText = CommandText.TenantRepairTargetTearDownCommandText; command.CommandType = CommandType.Text; command.ExecuteNonQuery( ); } }
/// <summary> /// Setups the specified context. /// </summary> /// <param name="context">The context.</param> void IDataTarget.Setup(IProcessingContext context) { if (context != null) { context.WriteInfo("Initializing..."); } using (EntryPointContext.UnsafeToIncludeEntryPoint( )) using (IDbCommand command = CreateCommand( )) { command.CommandText = CommandText.TenantMergeTargetSetupCommandText; command.CommandType = CommandType.Text; command.ExecuteNonQuery( ); } }
/// <summary> /// Setups the specified context. /// </summary> /// <param name="context">The context.</param> void IDataTarget.Setup(IProcessingContext context) { if (context != null) { context.WriteInfo("Initializing..."); } using (EntryPointContext.UnsafeToIncludeEntryPoint( )) using (IDbCommand command = CreateCommand( )) { command.CommandText = "CREATE TABLE #Binary ( OldDataHash NVARCHAR(MAX) COLLATE Latin1_General_CI_AI NULL, NewDataHash NVARCHAR(MAX) COLLATE Latin1_General_CI_AI NULL, FileExtension NVARCHAR(20) COLLATE Latin1_General_CI_AI NULL )"; command.CommandType = CommandType.Text; command.ExecuteNonQuery(); command.CommandText = "CREATE TABLE #Document ( OldDataHash NVARCHAR(MAX) COLLATE Latin1_General_CI_AI NULL, NewDataHash NVARCHAR(MAX) COLLATE Latin1_General_CI_AI NULL, FileExtension NVARCHAR(20) COLLATE Latin1_General_CI_AI NULL )"; command.CommandType = CommandType.Text; command.ExecuteNonQuery(); } }
/// <summary> /// Tears down. /// </summary> /// <param name="context">The context.</param> void IDataTarget.TearDown(IProcessingContext context) { if (context != null) { context.WriteInfo("Finalizing..."); } using (EntryPointContext.UnsafeToIncludeEntryPoint( )) using (IDbCommand command = CreateCommand( )) { command.CommandText = "DROP TABLE #Binary"; command.CommandType = CommandType.Text; command.ExecuteNonQuery( ); command.CommandText = "DROP TABLE #Document"; command.CommandType = CommandType.Text; command.ExecuteNonQuery( ); } }
/// <summary> /// Writes the binary data. /// </summary> /// <param name="data">The data.</param> /// <param name="context">The context.</param> void IDataTarget.WriteBinaryData(IEnumerable <BinaryDataEntry> data, IProcessingContext context) { int rowCount = 0; using (var command = CreateCommand( ) as SQLiteCommand) { Debug.Assert(command != null, "command != null"); const string sql = "insert into _Filestream_Binary (FileExtension, DataHash, Data) values (@fileExtension, @dataHash, @data)"; command.CommandType = CommandType.Text; command.CommandText = sql; foreach (BinaryDataEntry dataEntry in data) { if (dataEntry.Data == null) { context.WriteWarning($"Unexpected null values when updating binary table. DataHash:{dataEntry.DataHash}"); continue; } if (!IsBinaryDataValid(dataEntry.Data, dataEntry.DataHash)) { context.WriteWarning($"The binary data is corrupt. It will be skipped. The data hash does not match the expected value. DataHash:{dataEntry.DataHash}"); continue; } command.Parameters.Clear( ); command.Parameters.Add("@fileExtension", DbType.String).Value = dataEntry.FileExtension; command.Parameters.Add("@dataHash", DbType.String).Value = dataEntry.DataHash; byte[] compressedData = CompressionHelper.Compress(dataEntry.Data); command.Parameters.Add("@data", DbType.Binary, compressedData.Length).Value = compressedData; rowCount += command.ExecuteNonQuery( ); if (rowCount % 100 == 0) { context.WriteInfo($"Copying binary data... {rowCount} rows"); } } } }
/// <summary> /// Write in collection of field data. /// </summary> void IDataTarget.WriteFieldData(string dataTable, IEnumerable <DataEntry> data, IProcessingContext context) { bool isAliasTable = dataTable == "Alias"; // Converter for 'Bit' data to convert true/false to 1/0 Func <object, object> converter = GetDataConverter(dataTable); using (IDbCommand command = CreateCommand( )) { string sql = "insert into _Data_" + dataTable + " (EntityUid, FieldUid, Data) values (@entity, @field, @data)"; command.Parameters.Add(new SQLiteParameter("@entity", DbType.String)); command.Parameters.Add(new SQLiteParameter("@field", DbType.String)); command.Parameters.Add(new SQLiteParameter("@data", DbType.String)); if (isAliasTable) { sql = "insert into _Data_" + dataTable + " (EntityUid, FieldUid, Data, [Namespace], AliasMarkerId) values (@entity, @field, @data, @namespace, @marker)"; command.Parameters.Add(new SQLiteParameter("@namespace", DbType.String)); command.Parameters.Add(new SQLiteParameter("@marker", DbType.Int32)); } command.CommandText = sql; int rowCount = 0; foreach (DataEntry dataEntry in data) { (( SQLiteParameter )command.Parameters[0]).Value = dataEntry.EntityId; (( SQLiteParameter )command.Parameters[1]).Value = dataEntry.FieldId; (( SQLiteParameter )command.Parameters[2]).Value = converter(dataEntry.Data); if (isAliasTable) { (( SQLiteParameter )command.Parameters[3]).Value = dataEntry.Namespace; (( SQLiteParameter )command.Parameters[4]).Value = dataEntry.AliasMarkerId; } rowCount += command.ExecuteNonQuery( ); if (rowCount % 100 == 0) { context.WriteInfo($"Copying '{dataTable}' Field data... {rowCount} rows"); } } } }
void IDataTarget.WriteSecureData(IEnumerable <SecureDataEntry> data, IProcessingContext context) { int rowCount = 0; using (var command = CreateCommand() as SQLiteCommand) { Debug.Assert(command != null, "command != null"); const string sql = "insert into _SecureData (SecureId, Context, Data) values (@secureId, @context, @data)"; command.CommandType = CommandType.Text; command.CommandText = sql; foreach (SecureDataEntry dataEntry in data) { if (dataEntry.Data == null) { context.WriteWarning($"Unexpected null values when updating SecureData table. SecuredId:{dataEntry.SecureId}"); continue; } command.Parameters.Clear(); command.Parameters.Add("@secureId", DbType.String).Value = dataEntry.SecureId; command.Parameters.Add("@context", DbType.AnsiString).Value = dataEntry.Context; var encodedData = Convert.ToBase64String(dataEntry.Data); // encrypted so no point in compressing command.Parameters.Add("@data", DbType.String).Value = encodedData; rowCount += command.ExecuteNonQuery(); if (rowCount % 100 == 0) { context.WriteInfo($"Copying SecureData... {rowCount} rows"); } } } }
/// <summary> /// Write in collection of entities. /// </summary> void IDataTarget.WriteEntities(IEnumerable <EntityEntry> entities, IProcessingContext context) { using (IDbCommand command = CreateCommand( )) { const string sql = "insert into _Entity (Uid) values (@entityUid)"; command.CommandText = sql; command.Parameters.Add(new SQLiteParameter("@entityUid", DbType.String)); int rowCount = 0; foreach (EntityEntry entity in entities) { (( SQLiteParameter )command.Parameters[0]).Value = entity.EntityId; rowCount += command.ExecuteNonQuery( ); if (rowCount % 100 == 0) { context.WriteInfo($"Copying Entity data... {rowCount} rows"); } } } }
/// <summary> /// Migrates all data except metadata. /// </summary> private void MigrateContent(IProcessingContext context) { ///// // Migrate entities ///// context.WriteInfo("Copying Entity data..."); IEnumerable <EntityEntry> entities = DataSource.GetEntities(context); IList <EntityEntry> addedEntities = entities as IList <EntityEntry> ?? entities.ToList( ); context.Report.AddedEntities = addedEntities; Context.Report.Counts.Add(new StatisticsCount("Current Application Entities", addedEntities.Count, StatisticsCountType.CurrentApplication)); DataTarget.WriteEntities(addedEntities, context); ///// // Migrate relationships ///// context.WriteInfo("Copying Relationship data..."); IEnumerable <RelationshipEntry> relationships = DataSource.GetRelationships(context); IList <RelationshipEntry> relationshipEntries = relationships as IList <RelationshipEntry> ?? relationships.ToList( ); context.Report.AddedRelationships = relationshipEntries; Context.Report.Counts.Add(new StatisticsCount("Current Application Relationships", relationshipEntries.Count, StatisticsCountType.CurrentApplication)); DataTarget.WriteRelationships(relationshipEntries, context); ///// // Migrate field data ///// foreach (string fieldDataTable in Helpers.FieldDataTables) { context.WriteInfo(string.Format("Copying {0} Field data...", fieldDataTable)); IEnumerable <DataEntry> fieldData = DataSource.GetFieldData(fieldDataTable, context); IList <DataEntry> dataEntries = fieldData as IList <DataEntry> ?? fieldData.ToList( ); if (dataEntries.Count > 0) { context.Report.AddedEntityData[fieldDataTable] = dataEntries; } Context.Report.Counts.Add(new StatisticsCount(string.Format("Current Application {0} Data", fieldDataTable), dataEntries.Count, StatisticsCountType.CurrentApplication)); DataTarget.WriteFieldData(fieldDataTable, dataEntries, context); } context.WriteInfo("Copying Binary File data..."); IEnumerable <BinaryDataEntry> binaryData = DataSource.GetBinaryData(context); IList <BinaryDataEntry> binaryDataEntries = binaryData as IList <BinaryDataEntry> ?? binaryData.ToList( ); context.Report.AddedBinaryData = binaryDataEntries; Context.Report.Counts.Add(new StatisticsCount("Current Application Binary Data", binaryDataEntries.Count, StatisticsCountType.CurrentApplication)); DataTarget.WriteBinaryData(binaryDataEntries, context); context.WriteInfo("Copying Document File data..."); IEnumerable <DocumentDataEntry> documentData = DataSource.GetDocumentData(context); IList <DocumentDataEntry> documentDataEntries = documentData as IList <DocumentDataEntry> ?? documentData.ToList( ); context.Report.AddedDocumentData = documentDataEntries; Context.Report.Counts.Add(new StatisticsCount("Current Application Document Data", documentDataEntries.Count, StatisticsCountType.CurrentApplication)); DataTarget.WriteDocumentData(documentDataEntries, context); context.WriteInfo("Copying Secure data..."); IEnumerable <SecureDataEntry> secureData = DataSource.GetSecureData(context); IList <SecureDataEntry> secureDataEntries = secureData as IList <SecureDataEntry> ?? secureData.ToList( ); context.Report.AddedSecureData = secureDataEntries; Context.Report.Counts.Add(new StatisticsCount("Current Application Secure Data", secureDataEntries.Count, StatisticsCountType.CurrentApplication)); DataTarget.WriteSecureData(secureDataEntries, context); context.WriteInfo("Copying DoNotRemove data..."); IList <Guid> doNotRemove = DataSource.GetDoNotRemove(context).ToList( ); context.Report.AddedDoNotRemoveData = doNotRemove; Context.Report.Counts.Add(new StatisticsCount("Current Application DoNotRemove records", doNotRemove.Count, StatisticsCountType.CurrentApplication)); DataTarget.WriteDoNotRemove(doNotRemove, context); }
/// <summary> /// Gets the entities that should not be removed as part of an upgrade operation. /// </summary> /// <param name="context">The context.</param> /// <returns></returns> public IEnumerable <Guid> GetDoNotRemove(IProcessingContext context) { // Namely, get the entities that: // 1. used to be part of the application, // 2. which are no longer part of the application, // 3. which are still present in the tenant (i.e. the app developer removed them from the app, but didn't delete them altogether). // 4. we also need to carry over any entities marked as 'do not remove' in previous versions // Step 0: Determine what the original version of this package was Guid?packageId = OriginalPackageId; if (packageId == null) { context.WriteInfo("Tenant application does not have a package Id"); return(Enumerable.Empty <Guid>( )); } // Step 1: Get entities in the previous version LibraryAppSource appLibrarySource = new LibraryAppSource { AppVerId = packageId.Value }; IEnumerable <Guid> entitiesInPrevVersion = appLibrarySource .GetEntities(context) .Select(e => e.EntityId) .ToList( ); // Step 2: Disregard entities that are still in the application IEnumerable <Guid> entitiesInTenantApp = ((IDataSource)this) .GetEntities(context) .Select(e => e.EntityId); IEnumerable <Guid> missingEntities = entitiesInPrevVersion.Except(entitiesInTenantApp).ToList(); // Step 3: Check database to see if the entities are still present ISet <Guid> doNotRemove = new HashSet <Guid>( ); DataTable dt = TableValuedParameter.Create(TableValuedParameterType.Guid); foreach (var guid in missingEntities) { dt.Rows.Add(guid); } using (IDbCommand command = CreateCommand( )) { command.CommandText = "dbo.spGetEntityIdsByUpgradeIds"; command.CommandType = CommandType.StoredProcedure; command.AddParameter("@tenantId", DbType.Int64, TenantId); command.AddTableValuedParameter("@data", dt); using (IDataReader reader = command.ExecuteReader( )) { while (reader.Read( )) { var guid = reader.GetGuid(0); doNotRemove.Add(guid); // entities still in the tenant should be marked as 'do not remove' } } } // Step 4: Include entities marked as 'do not remove' in previous tenants var carriedOver = appLibrarySource.GetDoNotRemove(context); foreach (Guid guid in carriedOver) { doNotRemove.Add(guid); } // Entities that have been removed from the application, but still present in the tenant, // should get marked as 'do not delete' to indicate that when the application is upgraded it should not delete the entities. return(doNotRemove); }
/// <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> /// Set the application metadata. /// </summary> /// <param name="metadata"></param> /// <param name="context"></param> void IDataTarget.SetMetadata(Metadata metadata, IProcessingContext context) { if (SkipMetadata) { context.WriteInfo("Skipping app library metadata"); return; } ///// // Write the application ///// App app = Entity.GetByField <App>(metadata.AppId.ToString( ), new EntityRef("core:applicationId")).FirstOrDefault( ); if (app == null) { app = new App { Name = metadata.AppName, Description = metadata.Description, ApplicationId = metadata.AppId, Publisher = metadata.Publisher, PublisherUrl = metadata.PublisherUrl, ReleaseDate = metadata.ReleaseDate == DateTime.MinValue.ToUniversalTime() ? ( DateTime? )null : metadata.ReleaseDate.ToUniversalTime() }; app.Save( ); } ///// // Write the app-package ///// AppPackage package = app.ApplicationPackages.FirstOrDefault(ap => ap.AppVerId == metadata.AppVerId); if (package == null) { package = new AppPackage( ); var version = new Version(metadata.Version); AppPackage existingVersion = app.ApplicationPackages.FirstOrDefault(ap => ap.AppVersionString == version.ToString( )); bool versionExists = false; while (existingVersion != null) { versionExists = true; version = new Version(version.Major, version.Minor + 1); existingVersion = app.ApplicationPackages.FirstOrDefault(ap => ap.AppVersionString == version.ToString( )); } metadata.Version = version.ToString( ); if (versionExists) { context.WriteWarning("Version already exists.. incrementing"); } } else { package = package.AsWritable <AppPackage>( ); context.WriteWarning("Already installed.. overwriting"); } string solutionNames = app.InSolution?.Name; ///// // Localize the string values. ///// package.Name = string.Format("{0} Application Package {1}", solutionNames ?? app.Name, metadata.Version); package.Description = string.Format("Application Package for version {1} of {0}.", app.Name, metadata.Version); package.AppVersionString = metadata.Version; package.AppVerId = metadata.AppVerId; package.PackageForApplication = app; if (metadata.PublishDate != DateTime.MinValue && metadata.PublishDate > SqlDateTime.MinValue.Value) { package.PublishDate = metadata.PublishDate; } if (metadata.Dependencies != null) { IEntityCollection <AppPackageDependency> dependencies = new EntityCollection <AppPackageDependency>( ); foreach (SolutionDependency dependency in metadata.Dependencies) { AppPackageDependency appPackageDependency = new AppPackageDependency { Name = dependency.Name, AppPackageDependencyName = dependency.DependencyName, AppPackageDependencyId = dependency.DependencyApplication, AppPackageMinimumVersion = dependency.MinimumVersion == null ? null : dependency.MinimumVersion.ToString(4), AppPackageMaximumVersion = dependency.MaximumVersion == null ? null : dependency.MaximumVersion.ToString(4), AppPackageIsRequired = dependency.IsRequired }; dependencies.Add(appPackageDependency); } package.DependentAppPackageDetails = dependencies; } package.Save( ); }