Audit - verisoftCZ/verisoft-framework GitHub Wiki
#Obecné
- probíhá automaticky při Update, UpdateRange, Remove a RemoveRange (delete je soft, takže technicky probíhá update)
- porovnává se stará a nová hodnota každé property -- ignorují se property typu entita (na entitě Order mám property Customer, při update Order neaudituji Customera) -- do auditu jsou zahrnuty vnořené kolekce, kde se neporovnávají celé jednotlivé elementy, ale pouze jejich Idčka (Na entitě Customer mám kolekci Orders o dvou elementech s Ids 5 a 10. Připojím nový Order s Id 15 a odpojím order s Id 5. Do auditu se propíše| PropertyName: Orders | OldValue: 5;10 | NewValue: 10;15 |.)
- audit má jen dvě tabulky EntityAudit (hlavička) a EntityAuditDetail (záznamy změněných propert) ve schématu "audit", pro jedno API. Data zůstávají v databázi daného API kvůli bezpečnosti.
#Nastavení
- DbContext v API musí dědit od
Verisoft.Core.Data.EntityFramework.EntityFrameworkDbContextBase
-- kvůli registraci DbSetů auditních tabulek a jejich konfiguraci -- mohu také přenastavit DefaultSchema, které je přednastavené na "dbo" => fallback pokud se nepodaří vyčíst z konfigurace entity - Repozitář musí dědit od
Verisoft.Core.Data.EntityFramework.Repositories.EntityFrameworkRepositoryBase
a být implementacíVerisoft.Core.Common.Store.IRepository
-- je potřeba injectnout ILogger -- možnost nastavit chování, že v případě selhání auditu nedojde pouze k zalogování chyby auditu, ale i k throw => defaultně false -- možnost nastavit property, které se ignorují při porovnávání (case sensitive, doporučuji používatnameof(ClientEntity.VatId)
=> defaultně null
public abstract class BaseRepository<TEntity, TKey>(
IUnitOfWork unitOfWork,
IUserContext userContext,
ILogger<BaseRepository<TEntity, TKey>> logger,
bool throwIfAuditFails = false,
IEnumerable<string> ignoredProperties = null) : EntityFrameworkRepositoryBase<TEntity, TKey>(unitOfWork, userContext, logger, throwIfAuditFails, ignoredProperties)
where TEntity : class, IBaseEntity<TKey>
{
protected IDemoApiDbContext Context => (IDemoApiDbContext)UnitOfWork;
}
public class ClientRepository(
IDemoApiDbContext unitOfWork,
IUserContext userContext,
ILogger<ClientRepository> logger)
: BaseRepository<ClientEntity, int>(unitOfWork, userContext, logger, true, [nameof(ClientEntity.VatId), nameof(ClientEntity.CompanyActivity)]), IClientRepository
{
protected override DbSet<ClientEntity> GetDbSet()
{
return Context.Client;
}
}
#Get
- filtr
namespace Verisoft.Core.Contracts.Audit;
public class EntityAuditFilter
{
public string EntityId { get; set; }
public string ChangedBy { get; set; }
public DateTime? ChangedAtFrom { get; set; }
public DateTime? ChangedAtTo { get; set; }
public string[] ChangedAttributes { get; set; } = [];
public string OldValue { get; set; }
public string NewValue { get; set; }
}
- specification
-- pro detail
Verisoft.Core.Common.Audit.EntityAuditDetailSpecification
-- pro hlavičkuVerisoft.Core.Common.Audit.EntityAuditSpecification
- mapovací extension metody ve
Verisoft.Core.Common.Audit.MappingExtensions
-- filtr => specification hlavičky -- filtr => specification detailu -- audit entita => audit contract vč. zanořených detailů (i pro kolekci) -- audit detail entita => audit detail contract vč. vnořené hlavičky (i pro kolekci) -- audit detail entita => flattened audit detail (i pro kolekci)
public async Task<PagedData<EntityAudit>> GetAudit(FilteredRequest<EntityAuditFilter> request)
{
var specification = request.Filter.ToSpecification();
var filter = specification?.SatisfiedBy();
var count = await clientRepository.GetEntityAuditCountAsync(filter);
var audit = await clientRepository.GetPagedEntityAuditAsync<EntityAuditSort>(request.Offset, request.Limit, filter, request.Sort);
return new PagedData<EntityAudit>
{
Data = audit.ToContract(),
Total = count,
};
}
- metody
Task<IEnumerable<EntityAuditEntity>> GetPagedEntityAuditAsync<TSort>(int offset, int limit, Expression<Func<EntityAuditEntity, bool>> filter, SortDefinition sort, bool isAscendingSortByDefault = true)
where TSort : BaseSort<EntityAuditEntity>;
Task<IEnumerable<EntityAuditDetailEntity>> GetPagedEntityAuditDetailAsync<TSort>(int offset, int limit, Expression<Func<EntityAuditDetailEntity, bool>> filter, SortDefinition sort, bool isAscendingSortByDefault = true)
where TSort : BaseSort<EntityAuditDetailEntity>;
Task<int> GetEntityAuditCountAsync(Expression<Func<EntityAuditEntity, bool>> filter);
Task<int> GetEntityAuditDetailCountAsync(Expression<Func<EntityAuditDetailEntity, bool>> filter);