diff --git a/Directory.Packages.props b/Directory.Packages.props index 175b4c7..0c38d97 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,6 +3,8 @@ true + + diff --git a/HrynCo.DAL.Abstract/Entities/Entity.cs b/HrynCo.DAL.Abstract/Entities/Entity.cs index 84f8738..97777fe 100644 --- a/HrynCo.DAL.Abstract/Entities/Entity.cs +++ b/HrynCo.DAL.Abstract/Entities/Entity.cs @@ -14,12 +14,10 @@ public abstract class Entity : Entity protected Entity() { Id = Guid.NewGuid(); - Created = DateTimeOffset.UtcNow; } protected Entity(Guid id) { Id = id; - Created = DateTimeOffset.UtcNow; } } diff --git a/HrynCo.DAL.Abstract/Entities/IEntity.cs b/HrynCo.DAL.Abstract/Entities/IEntity.cs index 86c256a..33ba1d6 100644 --- a/HrynCo.DAL.Abstract/Entities/IEntity.cs +++ b/HrynCo.DAL.Abstract/Entities/IEntity.cs @@ -1,8 +1,12 @@ namespace HrynCo.DAL.Abstract.Entities; -public interface IEntity where TId : struct +public interface IEntity { - TId Id { get; set; } DateTimeOffset Created { get; set; } DateTimeOffset? Updated { get; set; } } + +public interface IEntity : IEntity where TId : struct +{ + TId Id { get; set; } +} diff --git a/HrynCo.DAL.EF/Converters/UtcValueConverter.cs b/HrynCo.DAL.EF/Converters/UtcValueConverter.cs new file mode 100644 index 0000000..8f2472c --- /dev/null +++ b/HrynCo.DAL.EF/Converters/UtcValueConverter.cs @@ -0,0 +1,6 @@ +namespace HrynCo.DAL.EF.Converters; + +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +internal class UtcValueConverter() + : ValueConverter(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); diff --git a/HrynCo.DAL.EF/Core/BaseDbContext.cs b/HrynCo.DAL.EF/Core/BaseDbContext.cs new file mode 100644 index 0000000..8120507 --- /dev/null +++ b/HrynCo.DAL.EF/Core/BaseDbContext.cs @@ -0,0 +1,74 @@ +namespace HrynCo.DAL.EF.Core; + +using HrynCo.Common; +using HrynCo.DAL.Abstract.Entities; +using HrynCo.DAL.EF.Converters; +using HrynCo.DAL.EF.Exceptions; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Metadata; + +public abstract class BaseDbContext : DbContext +{ + private readonly IClock _clock; + + protected BaseDbContext(DbContextOptions options, IClock clock) + : base(options) + { + _clock = clock; + } + + public override int SaveChanges() + { + ApplyTimestamps(); + return base.SaveChanges(); + } + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + ApplyTimestamps(); + return await base.SaveChangesAsync(cancellationToken); + } + + private void ApplyTimestamps() + { + DateTimeOffset now = _clock.UtcNow; + + foreach (EntityEntry entry in ChangeTracker.Entries()) + { + switch (entry.State) + { + case EntityState.Added: + entry.Entity.Created = now; + break; + case EntityState.Modified: + entry.Entity.Updated = now; + break; + case EntityState.Detached: + case EntityState.Unchanged: + case EntityState.Deleted: + break; + default: + throw new UnexpectedEntityStateException(entry.State); + } + } + } + + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder.Properties().HaveConversion(); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + foreach (IMutableForeignKey relationship in modelBuilder.Model.GetEntityTypes() + .SelectMany(e => e.GetForeignKeys())) + { + relationship.DeleteBehavior = DeleteBehavior.Restrict; + } + + modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly); + + base.OnModelCreating(modelBuilder); + } +} diff --git a/HrynCo.DAL.EF/Exceptions/UnexpectedEntityStateException.cs b/HrynCo.DAL.EF/Exceptions/UnexpectedEntityStateException.cs new file mode 100644 index 0000000..1b19c88 --- /dev/null +++ b/HrynCo.DAL.EF/Exceptions/UnexpectedEntityStateException.cs @@ -0,0 +1,11 @@ +namespace HrynCo.DAL.EF.Exceptions; + +using Microsoft.EntityFrameworkCore; + +public sealed class UnexpectedEntityStateException : Exception +{ + public UnexpectedEntityStateException(EntityState state) + : base($"Unexpected entity state: {state}") + { + } +} diff --git a/HrynCo.DAL.EF/HrynCo.DAL.EF.csproj b/HrynCo.DAL.EF/HrynCo.DAL.EF.csproj index 83eeae1..3624b64 100644 --- a/HrynCo.DAL.EF/HrynCo.DAL.EF.csproj +++ b/HrynCo.DAL.EF/HrynCo.DAL.EF.csproj @@ -18,6 +18,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive