From ef1e944a4c3834a208a32cfa891e07e41c06156a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20H=E1=BB=93ng=20Ph=C3=BAc?= Date: Thu, 8 Aug 2024 22:32:11 +0700 Subject: [PATCH] [BDB-1] Remove audit fields from entity (#7) --- Core/BuildingBlock.Core.Domain/BaseEntity.cs | 103 +----------------- .../EntityDeletedSpecification.cs | 21 ---- .../BaseDbContext.cs | 11 -- .../Extensions/DomainEventExtension.cs | 2 +- .../Extensions/FilterExtension.cs | 24 ---- .../Extensions/SaveChangesExtension.cs | 58 ---------- Tests/Core/Domain/BaseEntityTest.cs | 38 ------- .../Domain/Shared/Utils/EntityHelperTest.cs | 1 - 8 files changed, 4 insertions(+), 254 deletions(-) delete mode 100644 Core/BuildingBlock.Core.Domain/Specifications/Implementations/EntityDeletedSpecification.cs delete mode 100644 Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/FilterExtension.cs delete mode 100644 Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/SaveChangesExtension.cs diff --git a/Core/BuildingBlock.Core.Domain/BaseEntity.cs b/Core/BuildingBlock.Core.Domain/BaseEntity.cs index 2cb9537..4ecd4ca 100644 --- a/Core/BuildingBlock.Core.Domain/BaseEntity.cs +++ b/Core/BuildingBlock.Core.Domain/BaseEntity.cs @@ -1,7 +1,3 @@ -using System.Collections; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Reflection; using BuildingBlock.Core.Domain.DomainEvents; namespace BuildingBlock.Core.Domain; @@ -11,110 +7,17 @@ public interface IBaseEntity TKey Id { get; set; } } -public interface IDeleteEntity +public interface IEntity : IBaseEntity { - DateTime? DeletedAt { get; set; } - string? DeletedBy { get; set; } -} - -public interface IDeleteEntity : IDeleteEntity, IBaseEntity -{ -} - -public interface IAuditEntity -{ - DateTime CreatedAt { get; set; } - string CreatedBy { get; set; } - DateTime? UpdatedAt { get; set; } - string? UpdatedBy { get; set; } -} - -public interface IAuditEntity : IAuditEntity, IDeleteEntity -{ -} - -public abstract class BaseEntity : IBaseEntity -{ - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public virtual TKey Id { get; set; } = default!; -} - -public abstract class DeleteEntity : BaseEntity, IDeleteEntity -{ - public DateTime? DeletedAt { get; set; } - public string? DeletedBy { get; set; } -} - -public abstract class AuditEntity : DeleteEntity, IAuditEntity -{ - public DateTime CreatedAt { get; set; } - public string CreatedBy { get; set; } = null!; - public DateTime? UpdatedAt { get; set; } - public string? UpdatedBy { get; set; } } public interface IAggregateRoot : IEntity { } -public interface IEntity : IAuditEntity +public abstract class Entity : IEntity { -} - -public abstract class Entity : AuditEntity, IEntity -{ - public void ResetUpdatedTimeStamp() - { - UpdatedAt = null; - UpdatedBy = null; - } - - public void Delete(DateTime? deletedAt, string? deletedBy) - { - DeletedAt ??= deletedAt; - DeletedBy ??= deletedBy; - - var entityProperties = GetType().GetProperties(); - - foreach (var entityProperty in entityProperties) - if (IsAGenericList(entityProperty.PropertyType)) - DeleteProperty(entityProperty, deletedAt, deletedBy); - } - - private void DeleteProperty(PropertyInfo propertyInfo, DateTime? deletedAt, string? deletedBy) - { - var propertyValues = GetPropertyValues(propertyInfo); - - var deleteMethod = GetDeleteMethod(propertyInfo); - - if (deleteMethod == null) return; - - foreach (var value in propertyValues) - { - object?[] parameters = [deletedAt, deletedBy]; - deleteMethod.Invoke(value, parameters); - } - } - - private static MethodInfo? GetDeleteMethod(PropertyInfo propertyInfo) - { - var elementType = propertyInfo.PropertyType.GetGenericArguments()[0]; - - return elementType.GetMethod("Delete"); - } - - private IEnumerable GetPropertyValues(PropertyInfo propertyInfo) - { - var values = propertyInfo.GetValue(this); - - return values is null ? new List() : (IEnumerable)values; - } - - private static bool IsAGenericList(Type type) - { - return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>); - } + public Guid Id { get; set; } } public abstract class AggregateRoot : Entity, IAggregateRoot diff --git a/Core/BuildingBlock.Core.Domain/Specifications/Implementations/EntityDeletedSpecification.cs b/Core/BuildingBlock.Core.Domain/Specifications/Implementations/EntityDeletedSpecification.cs deleted file mode 100644 index e5a1e18..0000000 --- a/Core/BuildingBlock.Core.Domain/Specifications/Implementations/EntityDeletedSpecification.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Linq.Expressions; -using BuildingBlock.Core.Domain.Specifications.Abstractions; - -namespace BuildingBlock.Core.Domain.Specifications.Implementations; - -public class EntityDeletedSpecification : Specification where TEntity : IEntity -{ - private readonly bool _showDeleted; - - public EntityDeletedSpecification(bool showDeleted) - { - _showDeleted = showDeleted; - } - - public override Expression> ToExpression() - { - if (_showDeleted) return entity => true; - - return entity => entity.DeletedAt == null; - } -} \ No newline at end of file diff --git a/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/BaseDbContext.cs b/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/BaseDbContext.cs index fb2bcd5..c0d045a 100644 --- a/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/BaseDbContext.cs +++ b/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/BaseDbContext.cs @@ -1,5 +1,4 @@ using BuildingBlock.Core.Application; -using BuildingBlock.Core.Domain; using BuildingBlock.Infrastructure.EntityFrameworkCore.Extensions; using MediatR; using Microsoft.EntityFrameworkCore; @@ -20,22 +19,12 @@ protected BaseDbContext(DbContextOptions options, ICurrentUser currentUser, IMed protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); - - foreach (var entityType in builder.Model.GetEntityTypes()) - if (typeof(IEntity).IsAssignableFrom(entityType.ClrType)) - builder.SetSoftDeleteFilter(entityType.ClrType); } public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) { await _mediator.DispatchDomainEventsAsync(this); - var auditedEntities = ChangeTracker.Entries() - .Where(e => e is - { Entity: IEntity, State: EntityState.Added or EntityState.Modified or EntityState.Deleted }); - - auditedEntities.SetAuditProperties(_currentUser); - return await base.SaveChangesAsync(cancellationToken); } } \ No newline at end of file diff --git a/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/DomainEventExtension.cs b/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/DomainEventExtension.cs index e26ca9a..51ee0e1 100644 --- a/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/DomainEventExtension.cs +++ b/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/DomainEventExtension.cs @@ -8,7 +8,7 @@ public static class DomainEventExtension { public static async Task DispatchDomainEventsAsync(this IMediator mediator, DbContext dbContext) { - var domainEntities = dbContext.ChangeTracker.Entries().Where(x => x.Entity.DomainEvents.Any()) + var domainEntities = dbContext.ChangeTracker.Entries().Where(x => x.Entity.DomainEvents.Count != 0) .ToList(); var domainEvents = domainEntities.SelectMany(x => x.Entity.DomainEvents).ToList(); diff --git a/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/FilterExtension.cs b/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/FilterExtension.cs deleted file mode 100644 index 6d1ef40..0000000 --- a/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/FilterExtension.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Reflection; -using BuildingBlock.Core.Domain; -using Microsoft.EntityFrameworkCore; - -namespace BuildingBlock.Infrastructure.EntityFrameworkCore.Extensions; - -public static class FilterExtension -{ - private static readonly MethodInfo SetSoftDeleteFilterMethod = typeof(FilterExtension) - .GetMethods(BindingFlags.Public | BindingFlags.Static) - .Single(t => t is { IsGenericMethod: true, Name: "SetSoftDeleteFilter" }); - - public static void SetSoftDeleteFilter(this ModelBuilder modelBuilder, Type entityType) - { - SetSoftDeleteFilterMethod.MakeGenericMethod(entityType) - .Invoke(null, [modelBuilder]); - } - - public static void SetSoftDeleteFilter(this ModelBuilder modelBuilder) - where TEntity : class, IEntity - { - modelBuilder.Entity().HasQueryFilter(entity => entity.DeletedAt == null); - } -} \ No newline at end of file diff --git a/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/SaveChangesExtension.cs b/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/SaveChangesExtension.cs deleted file mode 100644 index 83f0c0e..0000000 --- a/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/SaveChangesExtension.cs +++ /dev/null @@ -1,58 +0,0 @@ -using BuildingBlock.Core.Application; -using BuildingBlock.Core.Domain; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; - -namespace BuildingBlock.Infrastructure.EntityFrameworkCore.Extensions; - -public static class SaveChangesExtension -{ - public static IEnumerable SetAuditProperties(this IEnumerable auditedEntities, - ICurrentUser currentUser) - { - var auditableEntities = auditedEntities.ToList(); - - foreach (var auditableEntity in auditableEntities) - { - var iEntity = (IEntity)auditableEntity.Entity; - var utcNow = DateTime.UtcNow; - var email = currentUser.Email ?? "guest"; - - switch (auditableEntity.State) - { - case EntityState.Added: - SetCreatedProperties(ref iEntity, utcNow, email); - break; - case EntityState.Modified: - SetModifiedProperties(ref iEntity, utcNow, email); - break; - case EntityState.Deleted: - SetDeletedProperties(ref iEntity, utcNow, email, auditableEntity); - break; - } - } - - return auditableEntities; - } - - private static void SetDeletedProperties(ref IEntity iEntity, DateTime utcNow, string email, - EntityEntry auditableEntity) - { - iEntity.DeletedAt ??= utcNow; - iEntity.DeletedBy ??= email; - - auditableEntity.State = EntityState.Modified; - } - - private static void SetModifiedProperties(ref IEntity iEntity, DateTime utcNow, string email) - { - iEntity.UpdatedAt ??= utcNow; - iEntity.UpdatedBy ??= email; - } - - private static void SetCreatedProperties(ref IEntity iEntity, DateTime utcNow, string email) - { - if (iEntity.CreatedAt == DateTime.MinValue) iEntity.CreatedAt = utcNow; - if (string.IsNullOrEmpty(iEntity.CreatedBy)) iEntity.CreatedBy = email; - } -} \ No newline at end of file diff --git a/Tests/Core/Domain/BaseEntityTest.cs b/Tests/Core/Domain/BaseEntityTest.cs index c66674b..bc33ddf 100644 --- a/Tests/Core/Domain/BaseEntityTest.cs +++ b/Tests/Core/Domain/BaseEntityTest.cs @@ -1,45 +1,7 @@ -using BuildingBlock.Core.Domain; using FluentAssertions; namespace Tests.Core.Domain; -public class EntityTest -{ - public class TestEntity : Entity - { - public TestEntity() - { - UpdatedAt = DateTime.Now; - UpdatedBy = "Test"; - } - - public List TestEntities { get; set; } = []; - public List TestInts { get; set; } = [1, 2, 3]; - public List TestStrings { get; set; } = []; - public TestEntity ChildEntity { get; set; } = null!; - } - - public class ResetUpdatedTimeStamp - { - public class ShouldResetUpdatedTimeStamp - { - [Fact] - public void WhenInvoke() - { - // Arrange - var entity = new TestEntity(); - - // Act - entity.ResetUpdatedTimeStamp(); - - // Assert - entity.UpdatedAt.Should().BeNull(); - entity.UpdatedBy.Should().BeNull(); - } - } - } -} - public class AggregateRootTest { public class AddDomainEvent diff --git a/Tests/Core/Domain/Shared/Utils/EntityHelperTest.cs b/Tests/Core/Domain/Shared/Utils/EntityHelperTest.cs index 3ed03bc..213d62a 100644 --- a/Tests/Core/Domain/Shared/Utils/EntityHelperTest.cs +++ b/Tests/Core/Domain/Shared/Utils/EntityHelperTest.cs @@ -1,4 +1,3 @@ -using BuildingBlock.Core.Domain; using BuildingBlock.Core.Domain.Exceptions; using BuildingBlock.Core.Domain.Repositories; using BuildingBlock.Core.Domain.Shared.Utils;