feat: add BaseDbContext, non-generic IEntity, fix Entity constructor #1

Merged
agrynco merged 1 commits from fix/entity-base-improvements into development 2026-05-05 22:03:21 +03:00
7 changed files with 100 additions and 4 deletions
Showing only changes of commit 6be7a0ceed - Show all commits
+2
View File
@@ -3,6 +3,8 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- HrynCo shared packages -->
<PackageVersion Include="HrynCo.Common" Version="1.0.0" />
<!-- Entity Framework Core -->
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5" />
-2
View File
@@ -14,12 +14,10 @@ public abstract class Entity : Entity<Guid>
protected Entity()
{
Id = Guid.NewGuid();
Created = DateTimeOffset.UtcNow;
}
protected Entity(Guid id)
{
Id = id;
Created = DateTimeOffset.UtcNow;
}
}
+6 -2
View File
@@ -1,8 +1,12 @@
namespace HrynCo.DAL.Abstract.Entities;
public interface IEntity<TId> where TId : struct
public interface IEntity
{
TId Id { get; set; }
DateTimeOffset Created { get; set; }
DateTimeOffset? Updated { get; set; }
}
public interface IEntity<TId> : IEntity where TId : struct
{
TId Id { get; set; }
}
@@ -0,0 +1,6 @@
namespace HrynCo.DAL.EF.Converters;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
internal class UtcValueConverter()
: ValueConverter<DateTime, DateTime>(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
+74
View File
@@ -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<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ApplyTimestamps();
return await base.SaveChangesAsync(cancellationToken);
}
private void ApplyTimestamps()
{
DateTimeOffset now = _clock.UtcNow;
foreach (EntityEntry<IEntity> entry in ChangeTracker.Entries<IEntity>())
{
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<DateTime>().HaveConversion<UtcValueConverter>();
}
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);
}
}
@@ -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}")
{
}
}
+1
View File
@@ -18,6 +18,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="HrynCo.Common" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>