| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- using System.Transactions;
- using Abp.Dependency;
- using Abp.Domain.Uow;
- using Abp.EntityHistory;
- using Abp.Events.Bus.Entities;
- using Abp.Extensions;
- using Abp.Json;
- using Abp.Reflection;
- using JetBrains.Annotations;
- using Microsoft.EntityFrameworkCore;
- using Microsoft.EntityFrameworkCore.ChangeTracking;
- using Microsoft.EntityFrameworkCore.Metadata;
- using VberZero.EntityHistory.Extensions;
- namespace VberZero.EntityHistory;
- public class EntityHistoryHelper : EntityHistoryHelperBase, IEntityHistoryHelper, ITransientDependency
- {
- public EntityHistoryHelper(
- IEntityHistoryConfiguration configuration,
- IUnitOfWorkManager unitOfWorkManager)
- : base(configuration, unitOfWorkManager)
- {
- }
- 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 virtual async Task SaveAsync(EntityChangeSet changeSet)
- {
- if (!IsEntityHistoryEnabled)
- {
- return;
- }
- UpdateChangeSet(changeSet);
- if (changeSet.EntityChanges.Count == 0)
- {
- return;
- }
- using var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress);
- await EntityHistoryStore.SaveAsync(changeSet);
- await uow.CompleteAsync();
- }
- 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();
- }
- }
- protected virtual string GetEntityId(EntityEntry entry)
- {
- var primaryKeys = entry.Properties.Where(p => p.Metadata.IsPrimaryKey());
- return primaryKeys.First().CurrentValue?.ToJsonString();
- }
- [CanBeNull]
- private EntityChange CreateEntityChange(EntityEntry entityEntry)
- {
- var entityId = GetEntityId(entityEntry);
- var entityTypeFullName = ProxyHelper.GetUnproxiedType(entityEntry.Entity).FullName;
- EntityChangeType changeType;
- switch (entityEntry.State)
- {
- case EntityState.Added:
- changeType = EntityChangeType.Created;
- break;
- case EntityState.Deleted:
- changeType = EntityChangeType.Deleted;
- break;
- case EntityState.Modified:
- changeType = entityEntry.IsDeleted() ? EntityChangeType.Deleted : EntityChangeType.Updated;
- break;
- case EntityState.Detached:
- case EntityState.Unchanged:
- return null;
- default:
- Logger.ErrorFormat("Unexpected {0} - {1}", nameof(entityEntry.State), entityEntry.State);
- return null;
- }
- if (entityId == null && changeType != EntityChangeType.Created)
- {
- Logger.ErrorFormat("EntityChangeType {0} must have non-empty entity id", changeType);
- return null;
- }
- return new EntityChange
- {
- ChangeType = changeType,
- EntityEntry = entityEntry, // [NotMapped]
- EntityId = entityId,
- EntityTypeFullName = entityTypeFullName,
- TenantId = AbpSession.TenantId
- };
- }
- /// <summary>
- /// Gets the property changes for this entry.
- /// </summary>
- private ICollection<EntityPropertyChange> GetPropertyChanges(EntityEntry entityEntry,
- bool auditedPropertiesOnly)
- {
- var propertyChanges = new List<EntityPropertyChange>();
- var properties = entityEntry.Metadata.GetProperties();
- foreach (var property in properties)
- {
- if (property.IsPrimaryKey())
- {
- continue;
- }
- var shouldSaveProperty = property.IsShadowProperty() // i.e. property.PropertyInfo == null
- ? !auditedPropertiesOnly
- : IsAuditedPropertyInfo(property.PropertyInfo) ?? !auditedPropertiesOnly;
- if (shouldSaveProperty)
- {
- var propertyEntry = entityEntry.Property(property.Name);
- propertyChanges.Add(
- CreateEntityPropertyChange(
- propertyEntry.GetOriginalValue(),
- propertyEntry.GetNewValue(),
- property
- )
- );
- }
- }
- return propertyChanges;
- }
- /// <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.IsShadowProperty()
- ? 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>();
- 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 = entityEntry.Metadata.GetForeignKeys().First(fk => fk.IsOwnership);
- propertyEntityType = ownerForeignKey.PrincipalEntityType.ClrType;
- }
- var isAuditedProperty = propertyEntry.Metadata.IsShadowProperty() ||
- IsAuditedPropertyInfo(propertyEntityType,
- propertyEntry.Metadata.PropertyInfo) == true;
- propertyChange.SetNewValue(propertyEntry.GetNewValue()?.ToJsonString());
- if (!isAuditedProperty || 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);
- }
- }
- private EntityPropertyChange CreateEntityPropertyChange(object oldValue, object newValue, IProperty property)
- {
- var entityPropertyChange = new EntityPropertyChange()
- {
- PropertyName = property.Name.TruncateWithPostfix(EntityPropertyChange.MaxPropertyNameLength),
- PropertyTypeFullName = property.ClrType.FullName.TruncateWithPostfix(
- EntityPropertyChange.MaxPropertyTypeFullNameLength
- ),
- TenantId = AbpSession.TenantId
- };
- entityPropertyChange.SetNewValue(newValue?.ToJsonString());
- entityPropertyChange.SetOriginalValue(oldValue?.ToJsonString());
- return entityPropertyChange;
- }
- }
|