Aktivity „michael.sudnik“

When editing a users permissions, there is still a big performance issue (no longer associated with the UI - thanks for that fix), but appears to be with the application services (PermissionAppService,PermissionManager and Permission Providers ).

For a brand new user (with no roles or OUs) it takes 15 seconds to load the Permissions dialog.

I have approximately 10 roles configured and approximately 30 OUs configured. Some of the OUs have roles associated with them, but as I said, none of these are associated with the newly created user.

Interestingly, overriding the PermissionManagementModal.cshtml with your changes seems to improved the Edit Permissions dialog when editing permissions for a Role. But, when I edit the permissions of a User it still has the performance problem. I have confiirmed that it is using the overriden view, so not sure why it is different.

Yep - that gives the same performance boost. Of course, it is much better to be able to use the asp-for tag helpers, so this is great. Thanks for the workaround.

I have also seen this performance hit when editing user or role permissions. It can take 15 seconds to load. I have also made the equivalent change to the PermissionManagementModal view and it took it down to 3 seconds.

Thanks for that. We actually purchased the upgrade to the business license recently, but the question credits did not increase with the upgrade. Maybe you could bump it up a bit more? Hopefully, I won't need them, but you never know.

Many thanks for your feedback.

I ended up extending my relevant entities to include a collection of OU ids and duplicated the same UI in the entity create / edit modal views (e.g. JTree), which exists for assigning a user to one or more OUs.

I then did what you suggested and created a custom claim type (added to user or role claims), which needed to be set to the id of the relevant OU that a user would have access to.

I then updated my repository to filter based on the relevant OU ids (discovered by checking the user claims and getting all child OUs using the OrganizationUnitManager.

I had to fix a bug which was preventing me from saving claims against individual roles.

Thanks again.

I was able to fix this myself by replacing the OrganizationUnitManagerservice and override the MoveAsync method so that it calledOrganizationUnitRepository.UpdateAsync after making any changes to properties of any OrganizationUnit.

One problem I had, was that I couldn't readily access the OrganizationUnit.Code or OrganizationUnit.ParentId property setters, as they were marked as internal. I had to use reflection to set the properties to combine the existing method functionaliy with the fix.

I'm not sure how it worked for you, as this layer of code is not database specific, and I wouldn't have expected it to work for Entity Framework either.

If anyone else is interested in doing this, here is my own implementation to add some simple entity change tracking by overriding the default MongoDbRepository class.

public class MongoDbEntityChangeRepository<TMongoDbContext, TEntity, TKey>
        : MongoDbRepository<TMongoDbContext, TEntity, TKey>
            , IAuditedRepository<TEntity, TKey>
        where TMongoDbContext : IAbpMongoDbContext
        where TEntity : class, IEntity<TKey>
    {
        public MongoDbEntityChangeRepository(IMongoDbContextProvider<TMongoDbContext> dbContextProvider)
            : base(dbContextProvider)
        {
        }

        private readonly object _serviceProviderLock = new object();

        private TRef LazyGetRequiredService<TRef>(Type serviceType, ref TRef reference)
        {
            if (reference == null)
            {
                lock (_serviceProviderLock)
                {
                    if (reference == null)
                    {
                        reference = (TRef)ServiceProvider.GetRequiredService(serviceType);
                    }
                }
            }

            return reference;
        }

        public EntityChangeLogger EntityChangeLogger => LazyGetRequiredService(typeof(EntityChangeLogger), ref _entityChangeLogger);
        private EntityChangeLogger _entityChangeLogger;


        /// <summary>
        /// Use this method to prevent fetching the entity again, if you already have it
        /// </summary>
        /// <param name="original"></param>
        /// <param name="updateEntity"></param>
        /// <param name="autoSave"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<TEntity> UpdateAsync(
            TEntity original,
            Action<TEntity> updateEntity,
            bool autoSave = false,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            var previous = EntityChangeLogger.CloneEntity<TEntity, TKey>(original);

            updateEntity(original);
            var updated = original;

            updated = await base.UpdateAsync(updated, autoSave, cancellationToken);

            EntityChangeLogger.LogEntityUpdated<TEntity, TKey>(previous, updated);

            return updated;
        }

        public override async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = new CancellationToken())
        {
            TEntity previous = null;

            bool entityHistoryEnabled = EntityChangeLogger.IsEntityHistoryEnabled<TEntity, TKey>();

            if (entityHistoryEnabled)
            {
                previous = await GetAsync(entity.Id, cancellationToken: cancellationToken);
            }

            var result = await base.UpdateAsync(entity, autoSave, cancellationToken);

            if (entityHistoryEnabled)
            {
                EntityChangeLogger.LogEntityUpdated<TEntity, TKey>(previous, entity);
            }

            return result;
        }

        public override async Task DeleteAsync(
            TEntity entity,
            bool autoSave = false,
            CancellationToken cancellationToken = default)
        {
            await base.DeleteAsync(entity, autoSave, cancellationToken);

            EntityChangeLogger.LogEntityDeleted<TEntity, TKey>(entity);
        }

        public override async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = new CancellationToken())
        {
            var result = await base.InsertAsync(entity, autoSave, cancellationToken);

            EntityChangeLogger.LogEntityCreated<TEntity, TKey>(result);

            return result;
        }
    }

public class EntityChangeLogger
    {
        private readonly IAuditingManager _auditingManager;
        private readonly IClock _clock;
        private readonly IAuditingHelper _auditingHelper;

        public EntityChangeLogger(IAuditingManager auditingManager, IClock clock, IAuditingHelper auditingHelper)
        {
            _auditingManager = auditingManager;
            _clock = clock;
            _auditingHelper = auditingHelper;
        }

        public void LogEntityCreated<T, TKey>(T current) where T : class, IEntity<TKey>
        {
            var entityType = typeof(T);

            if (!_auditingHelper.IsEntityHistoryEnabled(entityType))
                return;

            _auditingManager.Current.Log.EntityChanges.Add(new EntityChangeInfo()
            {
                ChangeTime = _clock.Now,
                ChangeType = EntityChangeType.Created,
                EntityId = GetEntityId(current),
                EntityTenantId = null,
                EntityTypeFullName = entityType.FullName,
                PropertyChanges = GetPropertyChanges<T, TKey>(null, current, true)
            });
        }

        public void LogEntityDeleted<T, TKey>(T previous) where T : class, IEntity<TKey>
        {
            var entityType = typeof(T);

            if (!_auditingHelper.IsEntityHistoryEnabled(entityType))
                return;

            _auditingManager.Current.Log.EntityChanges.Add(new EntityChangeInfo()
            {
                ChangeTime = _clock.Now,
                ChangeType = EntityChangeType.Deleted,
                EntityId = GetEntityId(previous),
                EntityTenantId = null,
                EntityTypeFullName = entityType.FullName,
                PropertyChanges = GetPropertyChanges<T, TKey>(previous, null, true)
            });
        }

        public bool IsEntityHistoryEnabled<T, TKey>() where T : class, IEntity<TKey>
        {
            var entityType = typeof(T);

            return _auditingHelper.IsEntityHistoryEnabled(entityType);
        }

        public void LogEntityUpdated<T, TKey>(T previous, T current) where T : class, IEntity<TKey>
        {
            var entityType = typeof(T);

            if (!IsEntityHistoryEnabled<T, TKey>())
                return;

            var notNullEntity = GetNotNull(previous, current);

            _auditingManager.Current.Log.EntityChanges.Add(new EntityChangeInfo()
            {
                ChangeTime = _clock.Now,
                ChangeType = EntityChangeType.Updated,
                EntityId = GetEntityId(notNullEntity),
                EntityTenantId = null,
                EntityTypeFullName = entityType.FullName,
                PropertyChanges = GetPropertyChanges<T, TKey>(previous, current, false)
            });
        }

        private static ConcurrentDictionary<Type, PropertyInfos> _propertyInfoCache = new ConcurrentDictionary<Type, PropertyInfos>();

        private class PropertyInfos
        {
            public PropertyInfos(PropertyInfo id, PropertyInfo extraProperties)
            {
                Id = id;
                ExtraProperties = extraProperties;
            }

            public PropertyInfo Id { get; private set; }
            public PropertyInfo ExtraProperties { get; private set; }
        }

        public T CloneEntity<T, TKey>(T entity) where T : class, IEntity<TKey>
        {
            // create a clone, without going back to the database again
            var doc = JsonSerializer.Serialize(entity);

            var clone = JsonSerializer.Deserialize<T>(doc);

            // set protected properties
            // - Id
            // - Extra Properties

            var entityType = typeof(T);

            var propertyInfo = _propertyInfoCache.GetOrAdd(entityType, CreatePropertyInfos<TKey>);

            propertyInfo.Id.SetValue(clone, entity.Id);

            if (entityType.IsAssignableTo<IHasExtraProperties>())
            {
                IHasExtraProperties e = (IHasExtraProperties)entity;

                propertyInfo.ExtraProperties.SetValue(clone, SimpleClone(e.ExtraProperties));
            }

            return clone;
        }

        private PropertyInfos CreatePropertyInfos<TKey>(Type entityType)
        {
            return new PropertyInfos(
                entityType.GetProperty(nameof(IEntity<TKey>.Id)),
                    entityType.GetProperty(nameof(IHasExtraProperties.ExtraProperties)));
        }

        private T SimpleClone<T>(T toClone)
        {
            var doc = JsonSerializer.Serialize(toClone);

            return JsonSerializer.Deserialize<T>(doc);
        }

        private List<EntityPropertyChangeInfo> GetPropertyChanges<T, TKey>(T previous, T current, bool logIfNoChange) where T : class, IEntity<TKey>
        {
            List<EntityPropertyChangeInfo> result = new List<EntityPropertyChangeInfo>();

            foreach (var propertyInfo in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                if (ShouldSavePropertyHistory(previous, current, propertyInfo, logIfNoChange, out string previousValue, out string currentValue))
                {
                    result.Add(new EntityPropertyChangeInfo
                    {
                        NewValue = currentValue,
                        OriginalValue = previousValue,
                        PropertyName = propertyInfo.Name,
                        PropertyTypeFullName = propertyInfo.PropertyType.FullName
                    });
                }
            }

            return result;
        }

        private bool ShouldSavePropertyHistory<T>(T previous, T current, PropertyInfo propertyInfo, bool logIfNoChange, out string previousValue, out string currentValue)
        {
            previousValue = null;
            currentValue = null;

            if (propertyInfo.IsDefined(typeof(DisableAuditingAttribute), true))
            {
                return false;
            }

            var entityType = typeof(T);

            if (entityType.IsDefined(typeof(DisableAuditingAttribute), true))
            {
                if (!propertyInfo.IsDefined(typeof(AuditedAttribute), true))
                {
                    return false;
                }
            }

            if (IsBaseAuditProperty(propertyInfo, entityType))
            {
                return false;
            }

            previousValue = previous != null ? JsonSerializer.Serialize(propertyInfo.GetValue(previous)).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength) : null;
            currentValue = current != null ? JsonSerializer.Serialize(propertyInfo.GetValue(current)).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength) : null;

            var isModified = !(previousValue?.Equals(currentValue) ?? currentValue == null);
            if (isModified)
            {
                return true;
            }

            return logIfNoChange;
        }

        private bool IsBaseAuditProperty(PropertyInfo propertyInfo, Type entityType)
        {
            if (entityType.IsAssignableTo<IHasCreationTime>()
                && propertyInfo.Name == nameof(IHasCreationTime.CreationTime))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IMayHaveCreator>()
                && propertyInfo.Name == nameof(IMayHaveCreator.CreatorId))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IMustHaveCreator>()
                && propertyInfo.Name == nameof(IMustHaveCreator.CreatorId))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IHasModificationTime>()
                && propertyInfo.Name == nameof(IHasModificationTime.LastModificationTime))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IModificationAuditedObject>()
                && propertyInfo.Name == nameof(IModificationAuditedObject.LastModifierId))
            {
                return true;
            }

            if (entityType.IsAssignableTo<ISoftDelete>()
                && propertyInfo.Name == nameof(ISoftDelete.IsDeleted))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IHasDeletionTime>()
                && propertyInfo.Name == nameof(IHasDeletionTime.DeletionTime))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IDeletionAuditedObject>()
                && propertyInfo.Name == nameof(IDeletionAuditedObject.DeleterId))
            {
                return true;
            }

            return false;
        }

        private object GetNotNull(object obj1, object obj2)
        {
            if (obj1 != null)
                return obj1;
            if (obj2 != null)
                return obj2;

            throw new NotSupportedException();
        }

        private string GetEntityId(object entityAsObj)
        {
            if (!(entityAsObj is IEntity entity))
            {
                throw new AbpException($"Entities should implement the {typeof(IEntity).AssemblyQualifiedName} interface! Given entity does not implement it: {entityAsObj.GetType().AssemblyQualifiedName}");
            }

            var keys = entity.GetKeys();
            if (keys.All(k => k == null))
            {
                return null;
            }

            return keys.JoinAsString(",");
        }

        protected virtual Guid? GetTenantId(object entity)
        {
            if (!(entity is IMultiTenant multiTenantEntity))
            {
                return null;
            }

            return multiTenantEntity.TenantId;
        }
    }

i have not yet tried this yet, but my instinct is that maybe this is a problem with the mongodb specific version. I have not made any modifications to the main application and only created custom modules, so I would have expected this to work. Which db did you use for your test? I will create a new solution and do some more investigating when i get a chance.

This appears to now be fixed in RC5

Zobrazených 11 až 20 z 31 záznamov
Made with ❤️ on ABP v8.2.0-preview Updated on marca 25, 2024, 15:11