Search code examples
abp-framework

How to get AbpEntityChanges data for each row of the entity table?


my application needs to show the entity change log for each row of the product table by clicking on a button in the row. But I don't know how to get the auditing data from the abpEntityChanges table.

the DTO I need for the frontend:

using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;

namespace App.Products
{
    public class EntityChangeDto : EntityDto<Guid> {
        
        public Guid AuditLogId {get; set;}
        public string EntityId {get; set;}
        public EntityChangeType ChangeType {get;set;}
        public DateTime ChangeTime {get; set;}
        public string UserName {get; set;}

    }
}

So I tried to make the IEntityChangeRepository first (derived from EntityChange class of the auditing module), and hoped to be able to use GetQueryableAsync in the AppService.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.AuditLogging;

namespace App.Products
{
    public interface IEntityChangeRepository : IRepository<EntityChange,Guid>
    {
        // need anything else here?
    }
}

an then added new task GetEntityChangeListAsync in my ProductAppService (of course also IProductAppService).

using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.AuditLogging;

namespace App.Products
{
    public class ProductAppService :
        CrudAppService<Product,ProductDto,Guid,GetProductListDto,CreateUpdateProductDto>,
        IProductAppService
    {
        private readonly IEntityChangeRepository _entityChangeRepository;
        private readonly IAuditLogRepository _auditLogRepository;

        public ProductAppService(
          IRepository<Product, Guid> repository
          IEntityChangeRepository entityChangeRepository,
          IAuditLogRepository auditLogRepository
        )
            : base(repository)
            {
                _entityChangeRepository = entityChangeRepository;
                _auditLogRepository = auditLogRepository;
            }
         
         public async Task<ListResultDto<EntityChangeDto>> GetEntityChangeListAsync(string id) {
            
             var queryable = await _entityChangeRepository.GetQueryableAsync();

             queryable = queryable
             .Where(p => p.EntityId.Equals(id))
             .OrderBy(p => p.ChangeTime);

             var query = from entityChange in queryable
                 join auditLog in _auditLogRepository on entityChange.AuditLogId equals auditLog.Id
                 select new { entityChange, auditLog };

             var queryResult = await AsyncExecuter.ToListAsync(query);

             var entityChangeDtos = queryResult.Select(x =>
                {
                    var entityChangeDto = ObjectMapper.Map<EntityChange,EntityChangeDto>(x.entityChange);
                    entityChangeDto.UserName = x.auditLog.UserName;
                    return entityChangeDto;
                }).ToList();
   
                return new ListResultDto<EntityChangeDto>(
                    entityChangeDtos
                );
            }
    }
}

But I keep getting error code 500. And I think the problem is most likely with the IEntityChangeRepository. Missing some mapping or the Repository is not queryable this way? Could someone help please? many thanks ^^ I am beginner in the coding world but did my research in abp documents, couldnt solve it though :)

Error log

2021-03-25 07:59:20.505 +01:00 [ERR] An exception was thrown while activating Castle.Proxies.ProductAppServiceProxy.
Autofac.Core.DependencyResolutionException: An exception was thrown while activating Castle.Proxies.ProductAppServiceProxy.
 ---> Autofac.Core.DependencyResolutionException: None of the constructors found with 'Volo.Abp.Autofac.AbpAutofacConstructorFinder' on type 'Castle.Proxies.ProductAppServiceProxy' can be invoked with the available services and parameters:
Cannot resolve parameter 'App.Products.IEntityChangeRepository entityChangeRepository' of constructor 'Void .ctor(Castle.DynamicProxy.IInterceptor[], Volo.Abp.Domain.Repositories.IRepository`2[App.Products.Product,System.Guid], App.Products.IEntityChangeRepository, Volo.Abp.AuditLogging.IAuditLogRepository)'.
   at Autofac.Core.Activators.Reflection.ReflectionActivator.GetAllBindings(ConstructorBinder[] availableConstructors, IComponentContext context, IEnumerable`1 parameters)
   at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters)
   at Autofac.Core.Activators.Reflection.ReflectionActivator.<ConfigurePipeline>b__11_0(ResolveRequestContext ctxt, Action`1 next)
   at Autofac.Core.Resolving.Middleware.DisposalTrackingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Builder.RegistrationBuilder`3.<>c__DisplayClass41_0.<PropertiesAutowired>b__0(ResolveRequestContext ctxt, Action`1 next)
   at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   --- End of inner exception stack trace ---
   at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Builder.RegistrationBuilder`3.<>c__DisplayClass35_0.<OnPreparing>b__0(ResolveRequestContext ctxt, Action`1 next)
   at Autofac.Core.Resolving.Middleware.CoreEventMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Middleware.SharingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Middleware.CircularDependencyDetectorMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request)
   at Autofac.Core.Resolving.ResolveOperation.ExecuteOperation(ResolveRequest request)
   at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
   at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters)
   at Microsoft.AspNetCore.Mvc.Controllers.ServiceBasedControllerActivator.Create(ControllerContext actionContext)
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass5_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
2021-03-25 07:59:20.507 +01:00 [ERR] ---------- Exception Data ----------
ActivatorChain = Castle.Proxies.ProductAppServiceProxy

Solution

  • This will help you to find your way;

    public class EfCoreAuditLogRepository : EfCoreRepository<IAuditLoggingDbContext, AuditLog, Guid>, IAuditLogRepository
        {
            public EfCoreAuditLogRepository(IDbContextProvider<IAuditLoggingDbContext> dbContextProvider)
                : base(dbContextProvider)
            {
    
            }
    
            public override async Task<IQueryable<AuditLog>> WithDetailsAsync()
            {
                return (await GetQueryableAsync()).IncludeDetails();
            }
    
            public virtual async Task<EntityChange> GetEntityChange(
                Guid entityChangeId,
                CancellationToken cancellationToken = default)
            {
                var entityChange = await (await GetDbContextAsync()).Set<EntityChange>()
                                        .AsNoTracking()
                                        .IncludeDetails()
                                        .Where(x => x.Id == entityChangeId)
                                        .OrderBy(x => x.Id)
                                        .FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
    
                if (entityChange == null)
                {
                    throw new EntityNotFoundException(typeof(EntityChange));
                }
    
                return entityChange;
            }
    
            public virtual async Task<List<EntityChange>> GetEntityChangeListAsync(
                string sorting = null,
                int maxResultCount = 50,
                int skipCount = 0,
                Guid? auditLogId = null,
                DateTime? startTime = null,
                DateTime? endTime = null,
                EntityChangeType? changeType = null,
                string entityId = null,
                string entityTypeFullName = null,
                bool includeDetails = false,
                CancellationToken cancellationToken = default)
            {
                var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName, includeDetails);
    
                return await query.OrderBy(sorting.IsNullOrWhiteSpace() ? (nameof(EntityChange.ChangeTime) + " DESC") : sorting)
                    .PageBy(skipCount, maxResultCount)
                    .ToListAsync(GetCancellationToken(cancellationToken));
            }
    
            public virtual async Task<long> GetEntityChangeCountAsync(
                Guid? auditLogId = null,
                DateTime? startTime = null,
                DateTime? endTime = null,
                EntityChangeType? changeType = null,
                string entityId = null,
                string entityTypeFullName = null,
                CancellationToken cancellationToken = default)
            {
                var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName);
    
                var totalCount = await query.LongCountAsync(GetCancellationToken(cancellationToken));
    
                return totalCount;
            }
    
            public virtual async Task<EntityChangeWithUsername> GetEntityChangeWithUsernameAsync(
                Guid entityChangeId,
                CancellationToken cancellationToken = default)
            {
                var auditLog = await (await GetDbSetAsync()).AsNoTracking().IncludeDetails()
                    .Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId)).FirstAsync(GetCancellationToken(cancellationToken));
    
                return new EntityChangeWithUsername()
                {
                    EntityChange = auditLog.EntityChanges.First(x => x.Id == entityChangeId),
                    UserName = auditLog.UserName
                };
            }
    
            protected virtual async Task<IQueryable<EntityChange>> GetEntityChangeListQueryAsync(
                Guid? auditLogId = null,
                DateTime? startTime = null,
                DateTime? endTime = null,
                EntityChangeType? changeType = null,
                string entityId = null,
                string entityTypeFullName = null,
                bool includeDetails = false)
            {
                return (await GetDbContextAsync())
                    .Set<EntityChange>()
                    .AsNoTracking()
                    .IncludeDetails(includeDetails)
                    .WhereIf(auditLogId.HasValue, e => e.AuditLogId == auditLogId)
                    .WhereIf(startTime.HasValue, e => e.ChangeTime >= startTime)
                    .WhereIf(endTime.HasValue, e => e.ChangeTime <= endTime)
                    .WhereIf(changeType.HasValue, e => e.ChangeType == changeType)
                    .WhereIf(!string.IsNullOrWhiteSpace(entityId), e => e.EntityId == entityId)
                    .WhereIf(!string.IsNullOrWhiteSpace(entityTypeFullName), e => e.EntityTypeFullName.Contains(entityTypeFullName));
            }
        }