public virtual void Save(EntityChangeSet changeSet)
 {
     _unitOfWorkManager.WithUnitOfWork(() =>
     {
         _changeSetRepository.Insert(changeSet);
     });
 }
 public virtual async Task SaveAsync(EntityChangeSet changeSet)
 {
     await _unitOfWorkManager.WithUnitOfWorkAsync(async() =>
     {
         await _changeSetRepository.InsertAsync(changeSet);
     });
 }
        /// <summary>
        /// Updates change time, entity id and foreign keys after SaveChanges is called.
        /// </summary>
        private void UpdateChangeSet(EntityChangeSet changeSet)
        {
            foreach (var entityChange in changeSet.EntityChanges)
            {
                /* Update change time */

                entityChange.ChangeTime = GetChangeTime(entityChange);

                /* Update entity id */

                var entityEntry = entityChange.EntityEntry.As <EntityEntry>();
                entityChange.EntityId = GetEntityId(entityEntry);

                /* Update foreign keys */

                var foreignKeys = entityEntry.Metadata.GetForeignKeys();

                foreach (var foreignKey in foreignKeys)
                {
                    foreach (var property in foreignKey.Properties)
                    {
                        var propertyEntry  = entityEntry.Property(property.Name);
                        var propertyChange = entityChange.PropertyChanges.FirstOrDefault(pc => pc.PropertyName == property.Name);

                        if (propertyChange == null)
                        {
                            if (!(propertyEntry.OriginalValue?.Equals(propertyEntry.CurrentValue) ?? propertyEntry.CurrentValue == null))
                            {
                                // Add foreign key
                                entityChange.PropertyChanges.Add(new EntityPropertyChange
                                {
                                    NewValue             = propertyEntry.CurrentValue.ToJsonString(),
                                    OriginalValue        = propertyEntry.OriginalValue.ToJsonString(),
                                    PropertyName         = property.Name,
                                    PropertyTypeFullName = property.ClrType.FullName
                                });
                            }

                            continue;
                        }

                        if (propertyChange.OriginalValue == propertyChange.NewValue)
                        {
                            var newValue = propertyEntry.CurrentValue.ToJsonString();
                            if (newValue == propertyChange.NewValue)
                            {
                                // No change
                                entityChange.PropertyChanges.Remove(propertyChange);
                            }
                            else
                            {
                                // Update foreign key
                                propertyChange.NewValue = newValue.TruncateWithPostfix(EntityPropertyChange.MaxValueLength);
                            }
                        }
                    }
                }
            }
        }
Beispiel #4
0
        public virtual EntityChangeSet CreateEntityChangeSet(DbContext context)
        {
            var changeSet = new EntityChangeSet
            {
                Reason = EntityChangeSetReasonProvider.Reason.TruncateWithPostfix(EntityChangeSet.MaxReasonLength),

                // Fill "who did this change"
                BrowserInfo          = ClientInfoProvider.BrowserInfo.TruncateWithPostfix(EntityChangeSet.MaxBrowserInfoLength),
                ClientIpAddress      = ClientInfoProvider.ClientIpAddress.TruncateWithPostfix(EntityChangeSet.MaxClientIpAddressLength),
                ClientName           = ClientInfoProvider.ComputerName.TruncateWithPostfix(EntityChangeSet.MaxClientNameLength),
                ImpersonatorTenantId = AbpSession.ImpersonatorTenantId,
                ImpersonatorUserId   = AbpSession.ImpersonatorUserId,
                TenantId             = AbpSession.TenantId,
                UserId = AbpSession.UserId
            };

            if (!IsEntityHistoryEnabled)
            {
                return(changeSet);
            }

            var objectContext       = ((IObjectContextAdapter)context).ObjectContext;
            var relationshipChanges = objectContext.ObjectStateManager
                                      .GetObjectStateEntries(EntityState.Added | EntityState.Deleted)
                                      .Where(state => state.IsRelationship)
                                      .ToList();

            foreach (var entityEntry in context.ChangeTracker.Entries())
            {
                var shouldSaveEntityHistory = ShouldSaveEntityHistory(entityEntry);
                if (!shouldSaveEntityHistory && !entityEntry.HasAuditedProperties())
                {
                    continue;
                }

                var entityType   = GetEntityType(objectContext, entityEntry.GetEntityBaseType());
                var entityChange = CreateEntityChange(entityEntry, entityType);
                if (entityChange == null)
                {
                    continue;
                }

                var entitySet       = GetEntitySet(objectContext, entityType);
                var propertyChanges = new List <EntityPropertyChange>();
                propertyChanges.AddRange(GetPropertyChanges(entityEntry, entityType, entitySet, shouldSaveEntityHistory));
                propertyChanges.AddRange(GetRelationshipChanges(entityEntry, entityType, entitySet, relationshipChanges, shouldSaveEntityHistory));

                if (!shouldSaveEntityHistory && propertyChanges.Count == 0)
                {
                    continue;
                }

                entityChange.PropertyChanges = propertyChanges;
                changeSet.EntityChanges.Add(entityChange);
            }

            return(changeSet);
        }
        public virtual void Save(EntityChangeSet changeSet)
        {
            if (!IsEntityHistoryEnabled)
            {
                return;
            }

            UpdateChangeSet(changeSet);

            if (changeSet.EntityChanges.Count == 0)
            {
                return;
            }

            using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
            {
                EntityHistoryStore.Save(changeSet);
                uow.Complete();
            }
        }
        public virtual async Task SaveAsync(EntityChangeSet changeSet)
        {
            if (!IsEntityHistoryEnabled)
            {
                return;
            }

            if (changeSet.EntityChanges.Count == 0)
            {
                return;
            }

            UpdateChangeSet(changeSet);

            using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.Suppress))
            {
                await EntityHistoryStore.SaveAsync(changeSet);

                await uow.CompleteAsync();
            }
        }
        public virtual EntityChangeSet CreateEntityChangeSet(ICollection <EntityEntry> entityEntries)
        {
            var changeSet = new EntityChangeSet
            {
                Reason = EntityChangeSetReasonProvider.Reason.TruncateWithPostfix(EntityChangeSet.MaxReasonLength),

                // Fill "who did this change"
                BrowserInfo          = ClientInfoProvider.BrowserInfo.TruncateWithPostfix(EntityChangeSet.MaxBrowserInfoLength),
                ClientIpAddress      = ClientInfoProvider.ClientIpAddress.TruncateWithPostfix(EntityChangeSet.MaxClientIpAddressLength),
                ClientName           = ClientInfoProvider.ComputerName.TruncateWithPostfix(EntityChangeSet.MaxClientNameLength),
                ImpersonatorTenantId = AbpSession.ImpersonatorTenantId,
                ImpersonatorUserId   = AbpSession.ImpersonatorUserId,
                TenantId             = AbpSession.TenantId,
                UserId = AbpSession.UserId
            };

            if (!IsEntityHistoryEnabled)
            {
                return(changeSet);
            }

            foreach (var entry in entityEntries)
            {
                var shouldSaveEntityHistory = ShouldSaveEntityHistory(entry);
                if (!shouldSaveEntityHistory && !HasAuditedProperties(entry))
                {
                    continue;
                }

                var entityChange = CreateEntityChange(entry, shouldSaveEntityHistory);
                if (entityChange == null)
                {
                    continue;
                }

                changeSet.EntityChanges.Add(entityChange);
            }

            return(changeSet);
        }
        /// <summary>
        /// Updates change time, entity id, Adds foreign keys, Removes/Updates property changes after SaveChanges is called.
        /// </summary>
        private void UpdateChangeSet(EntityChangeSet changeSet)
        {
            var entityChangesToRemove = new List <EntityChange>();

            foreach (var entityChange in changeSet.EntityChanges)
            {
                var entityEntry     = entityChange.EntityEntry.As <EntityEntry>();
                var entityEntryType = ProxyHelper.GetUnproxiedType(entityEntry.Entity);
                var isAuditedEntity = IsTypeOfAuditedEntity(entityEntryType) == true;

                /* Update change time */
                entityChange.ChangeTime = GetChangeTime(entityChange.ChangeType, entityEntry.Entity);

                /* Update entity id */
                entityChange.EntityId = GetEntityId(entityEntry);

                /* Update property changes */
                var trackedPropertyNames = entityChange.PropertyChanges.Select(pc => pc.PropertyName).ToList();

                var additionalForeignKeys = entityEntry.Metadata.GetDeclaredReferencingForeignKeys()
                                            .Where(fk => trackedPropertyNames.Contains(fk.Properties[0].Name))
                                            .ToList();

                /* Add additional foreign keys from navigation properties */
                foreach (var foreignKey in additionalForeignKeys)
                {
                    foreach (var property in foreignKey.Properties)
                    {
                        var shouldSaveProperty = property.PropertyInfo == null // Shadow properties or if mapped directly to a field
                            ? null
                            : IsAuditedPropertyInfo(entityEntryType, property.PropertyInfo);

                        if (shouldSaveProperty.HasValue && !shouldSaveProperty.Value)
                        {
                            continue;
                        }

                        var propertyEntry = entityEntry.Property(property.Name);

                        var newValue = propertyEntry.GetNewValue()?.ToJsonString();
                        var oldValue = propertyEntry.GetOriginalValue()?.ToJsonString();

                        // Add foreign key
                        entityChange.PropertyChanges.Add(CreateEntityPropertyChange(oldValue, newValue, property));
                    }
                }

                /* Update/Remove property changes */
                var propertyChangesToRemove = new List <EntityPropertyChange>();
                var foreignKeys             = entityEntry.Metadata.GetForeignKeys();
                foreach (var propertyChange in entityChange.PropertyChanges)
                {
                    var propertyEntry = entityEntry.Property(propertyChange.PropertyName);

                    // Take owner entity type if this is an owned entity
                    var propertyEntityType = entityEntryType;
                    if (entityEntry.Metadata.IsOwned())
                    {
                        var ownerForeignKey = foreignKeys.First(fk => fk.IsOwnership);
                        propertyEntityType = ownerForeignKey.PrincipalEntityType.ClrType;
                    }
                    var property          = propertyEntry.Metadata;
                    var isAuditedProperty = property.PropertyInfo != null &&
                                            (IsAuditedPropertyInfo(propertyEntityType, property.PropertyInfo) ?? false);
                    var isForeignKeyShadowProperty = property.IsShadowProperty() && foreignKeys.Any(fk => fk.Properties.Any(p => p.Name == propertyChange.PropertyName));

                    propertyChange.SetNewValue(propertyEntry.GetNewValue()?.ToJsonString());
                    if ((!isAuditedProperty && !isForeignKeyShadowProperty) || propertyChange.IsValuesEquals())
                    {
                        // No change
                        propertyChangesToRemove.Add(propertyChange);
                    }
                }

                foreach (var propertyChange in propertyChangesToRemove)
                {
                    entityChange.PropertyChanges.Remove(propertyChange);
                }

                if (!isAuditedEntity && entityChange.PropertyChanges.Count == 0)
                {
                    entityChangesToRemove.Add(entityChange);
                }
            }

            foreach (var entityChange in entityChangesToRemove)
            {
                changeSet.EntityChanges.Remove(entityChange);
            }
        }
        /// <summary>
        /// Updates change time, entity id, Adds foreign keys, Removes/Updates property changes after SaveChanges is called.
        /// </summary>
        private void UpdateChangeSet(EntityChangeSet changeSet)
        {
            var entityChangesToRemove = new List <EntityChange>();

            foreach (var entityChange in changeSet.EntityChanges)
            {
                var entityEntry     = entityChange.EntityEntry.As <EntityEntry>();
                var isAuditedEntity = IsTypeOfAuditedEntity(entityEntry.Entity.GetType()) == true;

                /* Update change time */
                entityChange.ChangeTime = GetChangeTime(entityChange.ChangeType, entityEntry.Entity);

                /* Update entity id */
                entityChange.EntityId = GetEntityId(entityEntry);

                /* Update property changes */
                var trackedPropertyNames        = entityChange.PropertyChanges.Select(pc => pc.PropertyName);
                var trackedNavigationProperties = entityEntry.Navigations
                                                  .Where(np => trackedPropertyNames.Contains(np.Metadata.Name))
                                                  .ToList();
                var additionalForeignKeys = trackedNavigationProperties
                                            .Where(np => !trackedPropertyNames.Contains(np.Metadata.Name))
                                            .Select(np => np.Metadata.ForeignKey)
                                            .Distinct()
                                            .ToList();

                /* Add additional foreign keys from navigation properties */
                foreach (var foreignKey in additionalForeignKeys)
                {
                    foreach (var property in foreignKey.Properties)
                    {
                        var shouldSaveProperty = property.IsShadowProperty() ?
                                                 null :
                                                 IsAuditedPropertyInfo(property.PropertyInfo);
                        if (shouldSaveProperty.HasValue && !shouldSaveProperty.Value)
                        {
                            continue;
                        }

                        var propertyEntry = entityEntry.Property(property.Name);
                        // TODO: fix new value comparison before truncation
                        var newValue = propertyEntry.GetNewValue()?.ToJsonString().TruncateWithPostfix(EntityPropertyChange.MaxValueLength);
                        var oldValue = propertyEntry.GetOriginalValue()?.ToJsonString().TruncateWithPostfix(EntityPropertyChange.MaxValueLength);
                        // Add foreign key
                        entityChange.PropertyChanges.Add(CreateEntityPropertyChange(oldValue, newValue, property));
                    }
                }

                /* Update/Remove property changes */
                var propertyChangesToRemove = new List <EntityPropertyChange>();
                foreach (var propertyChange in entityChange.PropertyChanges)
                {
                    var propertyEntry     = entityEntry.Property(propertyChange.PropertyName);
                    var isAuditedProperty = !propertyEntry.Metadata.IsShadowProperty() &&
                                            IsAuditedPropertyInfo(propertyEntry.Metadata.PropertyInfo) == true;

                    // TODO: fix new value comparison before truncation
                    propertyChange.NewValue = propertyEntry.GetNewValue()?.ToJsonString().TruncateWithPostfix(EntityPropertyChange.MaxValueLength);
                    if (!isAuditedProperty && propertyChange.OriginalValue == propertyChange.NewValue)
                    {
                        // No change
                        propertyChangesToRemove.Add(propertyChange);
                    }
                }

                foreach (var propertyChange in propertyChangesToRemove)
                {
                    entityChange.PropertyChanges.Remove(propertyChange);
                }

                if (!isAuditedEntity && entityChange.PropertyChanges.Count == 0)
                {
                    entityChangesToRemove.Add(entityChange);
                }
            }

            foreach (var entityChange in entityChangesToRemove)
            {
                changeSet.EntityChanges.Remove(entityChange);
            }
        }
 public void Save(EntityChangeSet entityChangeSet)
 {
 }
        /// <summary>
        /// Updates change time, entity id, Adds foreign keys, Removes/Updates property changes after SaveChanges is called.
        /// </summary>
        private void UpdateChangeSet(DbContext context, EntityChangeSet changeSet)
        {
            var entityChangesToRemove = new List <EntityChange>();

            foreach (var entityChange in changeSet.EntityChanges)
            {
                var objectContext   = context.As <IObjectContextAdapter>().ObjectContext;
                var entityEntry     = entityChange.EntityEntry.As <DbEntityEntry>();
                var typeOfEntity    = entityEntry.GetEntityBaseType();
                var isAuditedEntity = IsTypeOfAuditedEntity(typeOfEntity) == true;

                /* Update change time */
                entityChange.ChangeTime = GetChangeTime(entityChange.ChangeType, entityEntry.Entity);

                /* Update entity id */
                var entityType = GetEntityType(objectContext, typeOfEntity, useClrType: false);
                entityChange.EntityId = GetEntityId(entityEntry, entityType);

                /* Update property changes */
                var trackedPropertyNames        = entityChange.PropertyChanges.Select(pc => pc.PropertyName);
                var trackedNavigationProperties = entityType.NavigationProperties
                                                  .Where(np => trackedPropertyNames.Contains(np.Name))
                                                  .ToList();
                var additionalForeignKeys = trackedNavigationProperties
                                            .SelectMany(p => p.GetDependentProperties())
                                            .Where(p => !trackedPropertyNames.Contains(p.Name))
                                            .Distinct()
                                            .ToList();

                /* Add additional foreign keys from navigation properties */
                foreach (var foreignKey in additionalForeignKeys)
                {
                    var propertyEntry = entityEntry.Property(foreignKey.Name);
                    var propertyInfo  = entityEntry.GetPropertyInfo(foreignKey.Name);

                    var shouldSaveProperty = IsAuditedPropertyInfo(propertyInfo);
                    if (shouldSaveProperty.HasValue && !shouldSaveProperty.Value)
                    {
                        continue;
                    }

                    // TODO: fix new value comparison before truncation
                    var newValue = propertyEntry.GetNewValue()?.ToJsonString().TruncateWithPostfix(EntityPropertyChange.MaxValueLength);
                    var oldValue = propertyEntry.GetOriginalValue()?.ToJsonString().TruncateWithPostfix(EntityPropertyChange.MaxValueLength);

                    // Add foreign key
                    entityChange.PropertyChanges.Add(CreateEntityPropertyChange(oldValue, newValue, propertyInfo));
                }

                /* Update/Remove property changes */
                var propertyChangesToRemove = new List <EntityPropertyChange>();
                foreach (var propertyChange in entityChange.PropertyChanges)
                {
                    var memberEntry = entityEntry.Member(propertyChange.PropertyName);
                    if (!(memberEntry is DbPropertyEntry))
                    {
                        // Skipping other types of properties
                        // - Reference navigation properties (DbReferenceEntry)
                        // - Collection navigation properties (DbCollectionEntry)
                        continue;
                    }

                    var propertyEntry     = memberEntry.As <DbPropertyEntry>();
                    var propertyInfo      = entityEntry.GetPropertyInfo(propertyChange.PropertyName);
                    var isAuditedProperty = IsAuditedPropertyInfo(propertyInfo) == true;

                    // TODO: fix new value comparison before truncation
                    propertyChange.NewValue = propertyEntry.GetNewValue()?.ToJsonString().TruncateWithPostfix(EntityPropertyChange.MaxValueLength);
                    if (!isAuditedProperty && propertyChange.OriginalValue == propertyChange.NewValue)
                    {
                        // No change
                        propertyChangesToRemove.Add(propertyChange);
                    }
                }

                foreach (var propertyChange in propertyChangesToRemove)
                {
                    entityChange.PropertyChanges.Remove(propertyChange);
                }

                if (!isAuditedEntity && entityChange.PropertyChanges.Count == 0)
                {
                    entityChangesToRemove.Add(entityChange);
                }
            }

            foreach (var entityChange in entityChangesToRemove)
            {
                changeSet.EntityChanges.Remove(entityChange);
            }
        }
 public virtual void Save(EntityChangeSet changeSet)
 {
     _changeSetRepository.Insert(changeSet);
 }
 public virtual Task SaveAsync(EntityChangeSet changeSet)
 {
     return(_changeSetRepository.InsertAsync(changeSet));
 }
        public virtual EntityChangeSet CreateEntityChangeSet(ICollection <EntityEntry> entityEntries)
        {
            var changeSet = new EntityChangeSet
            {
                Reason = EntityChangeSetReasonProvider.Reason.TruncateWithPostfix(EntityChangeSet.MaxReasonLength),

                // Fill "who did this change"
                BrowserInfo     = ClientInfoProvider.BrowserInfo.TruncateWithPostfix(EntityChangeSet.MaxBrowserInfoLength),
                ClientIpAddress =
                    ClientInfoProvider.ClientIpAddress.TruncateWithPostfix(EntityChangeSet.MaxClientIpAddressLength),
                ClientName           = ClientInfoProvider.ComputerName.TruncateWithPostfix(EntityChangeSet.MaxClientNameLength),
                ImpersonatorTenantId = AbpSession.ImpersonatorTenantId,
                ImpersonatorUserId   = AbpSession.ImpersonatorUserId,
                TenantId             = AbpSession.TenantId,
                UserId = AbpSession.UserId
            };

            if (!IsEntityHistoryEnabled)
            {
                return(changeSet);
            }

            foreach (var entityEntry in entityEntries)
            {
                var typeOfEntity      = ProxyHelper.GetUnproxiedType(entityEntry.Entity);
                var shouldTrackEntity = IsTypeOfTrackedEntity(typeOfEntity);
                if (shouldTrackEntity.HasValue && !shouldTrackEntity.Value)
                {
                    continue;
                }

                if (!IsTypeOfEntity(typeOfEntity) && !entityEntry.Metadata.IsOwned())
                {
                    continue;
                }

                var shouldAuditEntity = IsTypeOfAuditedEntity(typeOfEntity);
                if (shouldAuditEntity.HasValue && !shouldAuditEntity.Value)
                {
                    continue;
                }

                bool?shouldAuditOwnerEntity   = null;
                bool?shouldAuditOwnerProperty = null;
                if (!shouldAuditEntity.HasValue && entityEntry.Metadata.IsOwned())
                {
                    // Check if owner entity has auditing attribute
                    var ownerForeignKey = entityEntry.Metadata.GetForeignKeys().First(fk => fk.IsOwnership);
                    var ownerEntityType = ownerForeignKey.PrincipalEntityType.ClrType;

                    shouldAuditOwnerEntity = IsTypeOfAuditedEntity(ownerEntityType);
                    if (shouldAuditOwnerEntity.HasValue && !shouldAuditOwnerEntity.Value)
                    {
                        continue;
                    }

                    var ownerPropertyInfo = ownerForeignKey.PrincipalToDependent.PropertyInfo;
                    shouldAuditOwnerProperty = IsAuditedPropertyInfo(ownerEntityType, ownerPropertyInfo);
                    if (shouldAuditOwnerProperty.HasValue && !shouldAuditOwnerProperty.Value)
                    {
                        continue;
                    }
                }

                var entityChange = CreateEntityChange(entityEntry);
                if (entityChange == null)
                {
                    continue;
                }

                var isAuditableEntity = (shouldAuditEntity.HasValue && shouldAuditEntity.Value) ||
                                        (shouldAuditOwnerEntity.HasValue && shouldAuditOwnerEntity.Value) ||
                                        (shouldAuditOwnerProperty.HasValue && shouldAuditOwnerProperty.Value);
                var isTrackableEntity = shouldTrackEntity.HasValue && shouldTrackEntity.Value;
                var shouldSaveAuditedPropertiesOnly = !isAuditableEntity && !isTrackableEntity;
                var propertyChanges = GetPropertyChanges(entityEntry, shouldSaveAuditedPropertiesOnly);
                if (propertyChanges.Count == 0)
                {
                    continue;
                }

                entityChange.PropertyChanges = propertyChanges;
                changeSet.EntityChanges.Add(entityChange);
            }

            return(changeSet);
        }
 public Task SaveAsync(EntityChangeSet entityChangeSet)
 {
     return(Task.CompletedTask);
 }
 public static void Save(this IEntityHistoryHelper entityHistoryHelper, EntityChangeSet changeSet)
 {
     AsyncHelper.RunSync(() => entityHistoryHelper.SaveAsync(changeSet));
 }
Beispiel #17
0
        /// <summary>
        /// Updates change time, entity id and foreign keys after SaveChanges is called.
        /// </summary>
        private void UpdateChangeSet(DbContext context, EntityChangeSet changeSet)
        {
            foreach (var entityChange in changeSet.EntityChanges)
            {
                /* Update change time */

                entityChange.ChangeTime = GetChangeTime(entityChange);

                /* Update entity id */

                var entityEntry = entityChange.EntityEntry.As <DbEntityEntry>();
                var entityType  = GetEntityType(context.As <IObjectContextAdapter>().ObjectContext, entityEntry.GetEntityBaseType(), useClrType: false);
                entityChange.EntityId = GetEntityId(entityEntry, entityType);

                /* Update foreign keys */

                var foreignKeys = entityType.NavigationProperties;

                foreach (var foreignKey in foreignKeys)
                {
                    foreach (var property in foreignKey.GetDependentProperties())
                    {
                        var propertyEntry  = entityEntry.Property(property.Name);
                        var propertyChange = entityChange.PropertyChanges.FirstOrDefault(pc => pc.PropertyName == property.Name);

                        //make sure test case cover post saving (for foreign key update)
                        if (propertyChange == null)
                        {
                            if (propertyEntry.HasChanged())
                            {
                                var propertyInfo = entityEntry.GetPropertyInfo(property.Name);

                                // Add foreign key
                                entityChange.PropertyChanges.Add(new EntityPropertyChange
                                {
                                    NewValue             = propertyEntry.CurrentValue.ToJsonString(),
                                    OriginalValue        = propertyEntry.OriginalValue.ToJsonString(),
                                    PropertyName         = property.Name,
                                    PropertyTypeFullName = propertyInfo.PropertyType.FullName
                                });
                            }

                            continue;
                        }

                        if (propertyChange.OriginalValue == propertyChange.NewValue)
                        {
                            var newValue = propertyEntry.GetNewValue().ToJsonString();
                            if (newValue == propertyChange.NewValue)
                            {
                                // No change
                                entityChange.PropertyChanges.Remove(propertyChange);
                            }
                            else
                            {
                                // Update foreign key
                                propertyChange.NewValue = newValue.TruncateWithPostfix(EntityPropertyChange.MaxValueLength);
                            }
                        }
                    }
                }
            }
        }