diff --git a/src/Digdir.Domain.Dialogporten.Application/Externals/IUnitOfWork.cs b/src/Digdir.Domain.Dialogporten.Application/Externals/IUnitOfWork.cs index 45af4164e..92db5aee3 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Externals/IUnitOfWork.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Externals/IUnitOfWork.cs @@ -7,7 +7,7 @@ namespace Digdir.Domain.Dialogporten.Application.Externals; public interface IUnitOfWork { - IUnitOfWork WithoutAuditableSideEffects(); + IUnitOfWork WithoutAggregateSideEffects(); Task SaveChangesAsync(CancellationToken cancellationToken = default); IUnitOfWork EnableConcurrencyCheck( diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs index 6766e0f32..e3c9d64e5 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs @@ -126,7 +126,7 @@ public async Task Handle(GetDialogQuery request, CancellationTo currentUserInformation.Name); var saveResult = await _unitOfWork - .WithoutAuditableSideEffects() + .WithoutAggregateSideEffects() .SaveChangesAsync(cancellationToken); saveResult.Switch( diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogQuery.cs index dd99329cf..f257f87a6 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogQuery.cs @@ -114,7 +114,7 @@ public async Task Handle(GetDialogQuery request, CancellationTo currentUserInformation.Name); var saveResult = await _unitOfWork - .WithoutAuditableSideEffects() + .WithoutAggregateSideEffects() .SaveChangesAsync(cancellationToken); saveResult.Switch( diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/UnitOfWork.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/UnitOfWork.cs index 9b0eaf858..65e37a643 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/UnitOfWork.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/UnitOfWork.cs @@ -28,7 +28,7 @@ internal sealed class UnitOfWork : IUnitOfWork, IAsyncDisposable, IDisposable private IDbContextTransaction? _transaction; - private bool _auditableSideEffects = true; + private bool _aggregateSideEffects = true; private bool _enableConcurrencyCheck; public UnitOfWork(DialogDbContext dialogDbContext, ITransactionTime transactionTime, IDomainContext domainContext) @@ -51,9 +51,9 @@ public IUnitOfWork EnableConcurrencyCheck( return this; } - public IUnitOfWork WithoutAuditableSideEffects() + public IUnitOfWork WithoutAggregateSideEffects() { - _auditableSideEffects = false; + _aggregateSideEffects = false; return this; } @@ -94,9 +94,11 @@ private async Task SaveChangesAsync_Internal(CancellationToke return new Success(); } - if (_auditableSideEffects) + _dialogDbContext.ChangeTracker.HandleAuditableEntities(_transactionTime.Value); + + if (_aggregateSideEffects) { - await _dialogDbContext.ChangeTracker.HandleAuditableEntities(_transactionTime.Value, cancellationToken); + await _dialogDbContext.ChangeTracker.HandleAggregateEntities(_transactionTime.Value, cancellationToken); } if (!_enableConcurrencyCheck) diff --git a/src/Digdir.Library.Entity.EntityFrameworkCore/EntityLibraryEfCoreExtensions.cs b/src/Digdir.Library.Entity.EntityFrameworkCore/EntityLibraryEfCoreExtensions.cs index 4b212565c..907a19a12 100644 --- a/src/Digdir.Library.Entity.EntityFrameworkCore/EntityLibraryEfCoreExtensions.cs +++ b/src/Digdir.Library.Entity.EntityFrameworkCore/EntityLibraryEfCoreExtensions.cs @@ -1,4 +1,5 @@ -using Digdir.Library.Entity.EntityFrameworkCore.Features.Creatable; +using Digdir.Library.Entity.Abstractions.Features.Aggregate; +using Digdir.Library.Entity.EntityFrameworkCore.Features.Creatable; using Digdir.Library.Entity.EntityFrameworkCore.Features.Identifiable; using Digdir.Library.Entity.EntityFrameworkCore.Features.Lookup; using Digdir.Library.Entity.EntityFrameworkCore.Features.SoftDeletable; @@ -8,6 +9,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Digdir.Library.Entity.Abstractions.Features.Creatable; using Digdir.Library.Entity.Abstractions.Features.Identifiable; +using Digdir.Library.Entity.Abstractions.Features.Immutable; using Digdir.Library.Entity.Abstractions.Features.Lookup; using Digdir.Library.Entity.Abstractions.Features.SoftDeletable; using Digdir.Library.Entity.Abstractions.Features.Updatable; @@ -23,16 +25,39 @@ namespace Digdir.Library.Entity.EntityFrameworkCore; /// public static class EntityLibraryEfCoreExtensions { + /// + /// Updates the properties and sets the correct on the for the entities implementing the following abstractions in context of aggregates. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// Should be called right before saving the entities. + /// + /// The change tracker. + /// The time in UTC in which the changes tok place. + /// A token for requesting cancellation of the operation. + /// The same instance so that multiple calls can be chained. + public static Task HandleAggregateEntities( + this ChangeTracker changeTracker, + DateTimeOffset utcNow, + CancellationToken cancellationToken = default) + => AggregateExtensions.HandleAggregateEntities(changeTracker, utcNow, cancellationToken); + /// /// Updates the properties and sets the correct on the for the entities implementing the following abstractions. /// - /// /// /// /// /// - /// - /// + /// + /// /// /// /// @@ -40,19 +65,14 @@ public static class EntityLibraryEfCoreExtensions /// /// The change tracker. /// The time in UTC in which the changes tok place. - /// A token for requesting cancellation of the operation. /// The same instance so that multiple calls can be chained. - public static async Task HandleAuditableEntities(this ChangeTracker changeTracker, DateTimeOffset utcNow, CancellationToken cancellationToken = default) - { - changeTracker.HandleLookupEntities() + public static ChangeTracker HandleAuditableEntities(this ChangeTracker changeTracker, DateTimeOffset utcNow) + => changeTracker.HandleLookupEntities() .HandleIdentifiableEntities() .HandleImmutableEntities() - //.HandleVersionableEntities() - .HandleCreatableEntities(utcNow); - //.HandleUpdatableEntities(utcNow); - await changeTracker.HandleAggregateEntities(utcNow, cancellationToken); - return changeTracker.HandleSoftDeletableEntities(utcNow); - } + .HandleCreatableEntities(utcNow) + .HandleUpdatableEntities(utcNow) + .HandleSoftDeletableEntities(utcNow); /// /// Configures the shape of, and how the entities implementing the following abstractions are mapped to the database. diff --git a/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Aggregate/AggregateExtensions.cs b/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Aggregate/AggregateExtensions.cs index 3a6085906..9963da4c1 100644 --- a/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Aggregate/AggregateExtensions.cs +++ b/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Aggregate/AggregateExtensions.cs @@ -14,7 +14,7 @@ internal static class AggregateExtensions { private static readonly EntityEntryComparer _entityEntryComparer = new(); - internal static async Task HandleAggregateEntities(this ChangeTracker changeTracker, + internal static async Task HandleAggregateEntities(this ChangeTracker changeTracker, DateTimeOffset utcNow, CancellationToken cancellationToken) { var aggregateNodeByEntry = await changeTracker @@ -57,6 +57,8 @@ internal static async Task HandleAggregateEntities(this ChangeTracker changeTrac versionable.NewVersion(); } } + + return changeTracker; } internal static ModelBuilder AddAggregateEntities(this ModelBuilder modelBuilder) diff --git a/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Updatable/UpdatableExtensions.cs b/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Updatable/UpdatableExtensions.cs index 5a0caa7a1..deead41f2 100644 --- a/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Updatable/UpdatableExtensions.cs +++ b/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Updatable/UpdatableExtensions.cs @@ -17,7 +17,8 @@ public static ChangeTracker HandleUpdatableEntities(this ChangeTracker changeTra { var updatableEntities = changeTracker .Entries() - .Where(x => x.State is EntityState.Added or EntityState.Modified && (x.Entity is not ISoftDeletableEntity deletable || !deletable.Deleted)); + .Where(IsNotSoftDeleted) + .Where(x => AddedWithoutExplicitUpdatedAt(x) || Modified(x)); foreach (var entity in updatableEntities) { @@ -26,4 +27,8 @@ public static ChangeTracker HandleUpdatableEntities(this ChangeTracker changeTra return changeTracker; } + + private static bool IsNotSoftDeleted(EntityEntry x) => x.Entity is not ISoftDeletableEntity { Deleted: true }; + private static bool AddedWithoutExplicitUpdatedAt(EntityEntry x) => x.State is EntityState.Added && x.Entity.UpdatedAt == default; + private static bool Modified(EntityEntry x) => x.State is EntityState.Modified; }