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