EF Core Configuration Generator
Overview
This skill generates Entity Framework Core configurations using Fluent API:
- IEntityTypeConfiguration<T> - Per-entity configuration classes
- Fluent API over attributes - Keep domain clean
- Snake case naming - PostgreSQL convention
- Relationships - One-to-Many, Many-to-Many, One-to-One
- Value Objects - Owned types mapping
Quick Reference
| Configuration | Use |
|---|---|
ToTable() | Table name |
HasKey() | Primary key |
Property() | Column configuration |
HasOne/HasMany() | Relationships |
OwnsOne() | Value objects |
HasIndex() | Database indexes |
Configuration Structure
/Infrastructure/Configurations/
├── {Entity}Configuration.cs
├── {ChildEntity}Configuration.cs
├── {ValueObject}Configuration.cs
└── OutboxMessageConfiguration.cs
Template: Basic Entity Configuration
// src/{name}.infrastructure/Configurations/{Entity}Configuration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using {name}.domain.{aggregate};
namespace {name}.infrastructure.configurations;
internal sealed class {Entity}Configuration : IEntityTypeConfiguration<{Entity}>
{
public void Configure(EntityTypeBuilder<{Entity}> builder)
{
// ═══════════════════════════════════════════════════════════════
// TABLE MAPPING
// ═══════════════════════════════════════════════════════════════
builder.ToTable("{entity}"); // snake_case table name
// ═══════════════════════════════════════════════════════════════
// PRIMARY KEY
// ═══════════════════════════════════════════════════════════════
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.ValueGeneratedNever(); // App generates GUIDs
// ═══════════════════════════════════════════════════════════════
// PROPERTIES
// ═══════════════════════════════════════════════════════════════
builder.Property(e => e.Name)
.HasMaxLength(100)
.IsRequired();
builder.Property(e => e.Description)
.HasColumnType("text"); // Unlimited length
builder.Property(e => e.IsActive)
.HasDefaultValue(true)
.IsRequired();
builder.Property(e => e.CreatedAt)
.IsRequired()
.HasDefaultValueSql("CURRENT_TIMESTAMP AT TIME ZONE 'UTC'");
builder.Property(e => e.UpdatedAt)
.IsRequired()
.HasDefaultValueSql("CURRENT_TIMESTAMP AT TIME ZONE 'UTC'");
// ═══════════════════════════════════════════════════════════════
// INDEXES
// ═══════════════════════════════════════════════════════════════
builder.HasIndex(e => e.Name)
.IsUnique();
builder.HasIndex(e => e.OrganizationId);
builder.HasIndex(e => new { e.OrganizationId, e.Name })
.IsUnique();
}
}
Template: Entity with Relationships
// src/{name}.infrastructure/Configurations/{Entity}Configuration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using {name}.domain.{aggregate};
namespace {name}.infrastructure.configurations;
internal sealed class {Entity}Configuration : IEntityTypeConfiguration<{Entity}>
{
public void Configure(EntityTypeBuilder<{Entity}> builder)
{
builder.ToTable("{entity}");
builder.HasKey(e => e.Id);
// ═══════════════════════════════════════════════════════════════
// FOREIGN KEY PROPERTIES
// ═══════════════════════════════════════════════════════════════
builder.Property(e => e.OrganizationId)
.IsRequired();
builder.Property(e => e.ParentId); // Nullable FK
// ═══════════════════════════════════════════════════════════════
// ONE-TO-MANY: Parent has many children
// ═══════════════════════════════════════════════════════════════
builder.HasMany(e => e.{ChildEntities})
.WithOne(c => c.{Entity})
.HasForeignKey(c => c.{Entity}Id)
.OnDelete(DeleteBehavior.Cascade);
// ═══════════════════════════════════════════════════════════════
// MANY-TO-ONE: Entity belongs to Organization
// ═══════════════════════════════════════════════════════════════
builder.HasOne(e => e.Organization)
.WithMany(o => o.{Entities})
.HasForeignKey(e => e.OrganizationId)
.OnDelete(DeleteBehavior.Restrict); // Prevent cascade delete
// ═══════════════════════════════════════════════════════════════
// SELF-REFERENCING: Entity has optional parent
// ═══════════════════════════════════════════════════════════════
builder.HasOne(e => e.Parent)
.WithMany(e => e.Children)
.HasForeignKey(e => e.ParentId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
}
}
Template: Child Entity Configuration
// src/{name}.infrastructure/Configurations/{ChildEntity}Configuration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using {name}.domain.{aggregate};
namespace {name}.infrastructure.configurations;
internal sealed class {ChildEntity}Configuration : IEntityTypeConfiguration<{ChildEntity}>
{
public void Configure(EntityTypeBuilder<{ChildEntity}> builder)
{
builder.ToTable("{child_entity}");
builder.HasKey(c => c.Id);
builder.Property(c => c.Id)
.ValueGeneratedNever();
builder.Property(c => c.{Parent}Id)
.IsRequired();
builder.Property(c => c.Name)
.HasMaxLength(100)
.IsRequired();
builder.Property(c => c.SortOrder)
.IsRequired()
.HasDefaultValue(0);
builder.Property(c => c.CreatedAt)
.IsRequired()
.HasDefaultValueSql("CURRENT_TIMESTAMP AT TIME ZONE 'UTC'");
// Relationship defined from parent side, but can also define here
builder.HasOne(c => c.{Parent})
.WithMany(p => p.{ChildEntities})
.HasForeignKey(c => c.{Parent}Id)
.OnDelete(DeleteBehavior.Cascade);
// Composite unique constraint
builder.HasIndex(c => new { c.{Parent}Id, c.Name })
.IsUnique();
builder.HasIndex(c => new { c.{Parent}Id, c.SortOrder });
}
}
Template: Many-to-Many Relationship
// src/{name}.infrastructure/Configurations/User{Entity}Configuration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using {name}.domain.{aggregate};
namespace {name}.infrastructure.configurations;
// Join entity for many-to-many
internal sealed class User{Entity}Configuration : IEntityTypeConfiguration<User{Entity}>
{
public void Configure(EntityTypeBuilder<User{Entity}> builder)
{
builder.ToTable("user_{entity}");
// Composite primary key
builder.HasKey(ue => new { ue.UserId, ue.{Entity}Id });
// Or with separate ID
// builder.HasKey(ue => ue.Id);
// builder.HasIndex(ue => new { ue.UserId, ue.{Entity}Id }).IsUnique();
builder.Property(ue => ue.UserId)
.IsRequired();
builder.Property(ue => ue.{Entity}Id)
.IsRequired();
builder.Property(ue => ue.IsManager)
.HasDefaultValue(false);
builder.Property(ue => ue.CreatedAt)
.HasDefaultValueSql("CURRENT_TIMESTAMP AT TIME ZONE 'UTC'");
// Relationships
builder.HasOne(ue => ue.User)
.WithMany(u => u.User{Entities})
.HasForeignKey(ue => ue.UserId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(ue => ue.{Entity})
.With