Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify DbContext by extracting aggregate configuration into dedicated classes #676

Merged
merged 3 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions application/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<PackageVersion Include="Bogus" Version="35.6.1" />
<PackageVersion Include="FluentAssertions" Version="6.12.2" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="11.10.0" />
<PackageVersion Include="Humanizer.Core" Version="2.14.1" />
<PackageVersion Include="IdGen" Version="3.0.7" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageVersion Include="Mapster" Version="7.4.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,8 @@
using Microsoft.EntityFrameworkCore;
using PlatformPlatform.AccountManagement.Features.Authentication.Domain;
using PlatformPlatform.AccountManagement.Features.Signups.Domain;
using PlatformPlatform.AccountManagement.Features.Tenants.Domain;
using PlatformPlatform.AccountManagement.Features.Users.Domain;
using PlatformPlatform.SharedKernel.Domain;
using PlatformPlatform.SharedKernel.EntityFramework;
using PlatformPlatform.SharedKernel.ExecutionContext;

namespace PlatformPlatform.AccountManagement.Database;

public sealed class AccountManagementDbContext(DbContextOptions<AccountManagementDbContext> options, IExecutionContext executionContext)
: SharedKernelDbContext<AccountManagementDbContext>(options, executionContext)
{
public DbSet<Login> Logins => Set<Login>();

public DbSet<Signup> Signups => Set<Signup>();

public DbSet<Tenant> Tenants => Set<Tenant>();

public DbSet<User> Users => Set<User>();

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

// Login
modelBuilder.MapStronglyTypedId<Login, LoginId, string>(t => t.Id);
modelBuilder.MapStronglyTypedId<Login, TenantId, string>(u => u.TenantId);
modelBuilder.MapStronglyTypedUuid<Login, UserId>(u => u.UserId);

// Signup
modelBuilder.MapStronglyTypedUuid<Signup, SignupId>(a => a.Id);
modelBuilder.MapStronglyTypedNullableId<Signup, TenantId, string>(u => u.TenantId);

// Tenant
modelBuilder.MapStronglyTypedId<Tenant, TenantId, string>(t => t.Id);

// User
modelBuilder.MapStronglyTypedUuid<User, UserId>(u => u.Id);
modelBuilder.MapStronglyTypedId<User, TenantId, string>(u => u.TenantId);
modelBuilder.Entity<User>()
.OwnsOne(e => e.Avatar, b => b.ToJson())
.HasOne<Tenant>()
.WithMany()
.HasForeignKey(u => u.TenantId)
.HasPrincipalKey(t => t.Id);
}
}
: SharedKernelDbContext<AccountManagementDbContext>(options, executionContext);
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using PlatformPlatform.SharedKernel.Domain;
using PlatformPlatform.SharedKernel.EntityFramework;

namespace PlatformPlatform.AccountManagement.Features.Authentication.Domain;

public sealed class LoginConfiguration : IEntityTypeConfiguration<Login>
{
public void Configure(EntityTypeBuilder<Login> builder)
{
builder.MapStronglyTypedId<Login, LoginId, string>(t => t.Id);
builder.MapStronglyTypedId<Login, TenantId, string>(u => u.TenantId);
builder.MapStronglyTypedUuid<Login, UserId>(u => u.UserId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using PlatformPlatform.SharedKernel.Domain;
using PlatformPlatform.SharedKernel.EntityFramework;

namespace PlatformPlatform.AccountManagement.Features.Signups.Domain;

public sealed class SignupConfiguration : IEntityTypeConfiguration<Signup>
{
public void Configure(EntityTypeBuilder<Signup> builder)
{
builder.MapStronglyTypedUuid<Signup, SignupId>(a => a.Id);
builder.MapStronglyTypedNullableId<Signup, TenantId, string>(u => u.TenantId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public sealed class SignupRepository(AccountManagementDbContext accountManagemen
{
public Signup[] GetByEmailOrTenantId(TenantId tenantId, string email)
{
return accountManagementDbContext.Signups
return DbSet
.Where(r => !r.Completed)
.Where(r => r.TenantId == tenantId || r.Email == email.ToLowerInvariant())
.ToArray();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using PlatformPlatform.SharedKernel.Domain;
using PlatformPlatform.SharedKernel.EntityFramework;

namespace PlatformPlatform.AccountManagement.Features.Tenants.Domain;

public sealed class TenantConfiguration : IEntityTypeConfiguration<Tenant>
{
public void Configure(EntityTypeBuilder<Tenant> builder)
{
builder.MapStronglyTypedId<Tenant, TenantId, string>(t => t.Id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using PlatformPlatform.AccountManagement.Features.Tenants.Domain;
using PlatformPlatform.SharedKernel.Domain;
using PlatformPlatform.SharedKernel.EntityFramework;

namespace PlatformPlatform.AccountManagement.Features.Users.Domain;

public sealed class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.MapStronglyTypedUuid<User, UserId>(u => u.Id);
builder.MapStronglyTypedId<User, TenantId, string>(u => u.TenantId);
builder
.OwnsOne(e => e.Avatar, b => b.ToJson())
.HasOne<Tenant>()
.WithMany()
.HasForeignKey(u => u.TenantId)
.HasPrincipalKey(t => t.Id);
}
}
4 changes: 2 additions & 2 deletions application/account-management/Tests/DatabaseSeeder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public sealed class DatabaseSeeder
public DatabaseSeeder(AccountManagementDbContext accountManagementDbContext)
{
Tenant1 = Tenant.Create(new TenantId("tenant-1"), "[email protected]");
accountManagementDbContext.Tenants.AddRange(Tenant1);
accountManagementDbContext.Set<Tenant>().AddRange(Tenant1);
User1 = User.Create(Tenant1.Id, "[email protected]", UserRole.Owner, true, null);
accountManagementDbContext.Users.AddRange(User1);
accountManagementDbContext.Set<User>().AddRange(User1);

accountManagementDbContext.SaveChanges();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using PlatformPlatform.SharedKernel.StronglyTypedIds;

Expand All @@ -11,37 +12,34 @@ public static class ModelBuilderExtensions
/// This method is used to tell Entity Framework how to map a strongly typed ID to a SQL column using the
/// underlying type of the strongly-typed ID.
/// </summary>
public static void MapStronglyTypedLongId<T, TId>(this ModelBuilder modelBuilder, Expression<Func<T, TId>> expression)
public static void MapStronglyTypedLongId<T, TId>(this EntityTypeBuilder<T> builder, Expression<Func<T, TId>> expression)
where T : class where TId : StronglyTypedLongId<TId>
{
modelBuilder
.Entity<T>()
builder
.Property(expression)
.HasConversion(v => v.Value, v => (Activator.CreateInstance(typeof(TId), v) as TId)!);
}

public static void MapStronglyTypedUuid<T, TId>(this ModelBuilder modelBuilder, Expression<Func<T, TId>> expression)
public static void MapStronglyTypedUuid<T, TId>(this EntityTypeBuilder<T> builder, Expression<Func<T, TId>> expression)
where T : class where TId : StronglyTypedUlid<TId>
{
modelBuilder
.Entity<T>()
builder
.Property(expression)
.HasConversion(v => v.Value, v => (Activator.CreateInstance(typeof(TId), v) as TId)!);
}

public static void MapStronglyTypedId<T, TId, TValue>(this ModelBuilder modelBuilder, Expression<Func<T, TId>> expression)
public static void MapStronglyTypedId<T, TId, TValue>(this EntityTypeBuilder<T> builder, Expression<Func<T, TId>> expression)
where T : class
where TValue : IComparable<TValue>
where TId : StronglyTypedId<TValue, TId>
{
modelBuilder
.Entity<T>()
builder
.Property(expression)
.HasConversion(v => v.Value, v => (Activator.CreateInstance(typeof(TId), v) as TId)!);
}

public static void MapStronglyTypedNullableId<T, TId, TValue>(
this ModelBuilder modelBuilder,
this EntityTypeBuilder<T> builder,
Expression<Func<T, TId?>> idExpression
)
where T : class
Expand All @@ -54,8 +52,7 @@ public static void MapStronglyTypedNullableId<T, TId, TValue>(
var idCoalesceExpression =
Expression.Lambda<Func<TId, TValue>>(Expression.Coalesce(idValueProperty, nullConstant), idParameter);

modelBuilder
.Entity<T>()
builder
.Property(idExpression)
.HasConversion(idCoalesceExpression!, v => Activator.CreateInstance(typeof(TId), v) as TId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq.Expressions;
using Humanizer;
using Microsoft.EntityFrameworkCore;
using PlatformPlatform.SharedKernel.Domain;
using PlatformPlatform.SharedKernel.ExecutionContext;
Expand All @@ -24,6 +25,15 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(TContext).Assembly);

// Set pluralized table names for all aggregates
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var tableName = entityType.GetTableName()!.Pluralize();
entityType.SetTableName(tableName);
}

// Ensures that all enum properties are stored as strings in the database.
modelBuilder.UseStringForEnums();

Expand Down
1 change: 1 addition & 0 deletions application/shared-kernel/SharedKernel/SharedKernel.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<PackageReference Include="Azure.Security.KeyVault.Keys" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" />
<PackageReference Include="Humanizer.Core" />
<PackageReference Include="IdGen" />
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="Mapster" />
Expand Down
Loading