| name | add-entity |
| description | Create a domain entity with multi-tenancy, auditing, soft-delete, and domain events. Use when adding new database entities to a module. |
Add Entity
Create a domain entity following FSH patterns with full multi-tenancy support.
Entity Template
public sealed class {Entity} : AggregateRoot<Guid>, IHasTenant, IAuditableEntity, ISoftDeletable
{
public string Name { get; private set; } = null!;
public decimal Price { get; private set; }
public string? Description { get; private set; }
public string TenantId { get; private set; } = null!;
public DateTimeOffset CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTimeOffset? LastModifiedAt { get; set; }
public string? LastModifiedBy { get; set; }
public DateTimeOffset? DeletedAt { get; set; }
public string? DeletedBy { get; set; }
private {Entity}() { }
public static {Entity} Create(string name, decimal price, string tenantId)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(price);
var entity = new {Entity}
{
Id = Guid.NewGuid(),
Name = name,
Price = price,
TenantId = tenantId
};
entity.AddDomainEvent(new {Entity}CreatedEvent(entity.Id));
return entity;
}
public void UpdateDetails(string name, decimal price, string? description)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(price);
Name = name;
Price = price;
Description = description;
AddDomainEvent(new {Entity}UpdatedEvent(Id));
}
}
Domain Events
public sealed record {Entity}CreatedEvent(Guid {Entity}Id) : IDomainEvent;
public sealed record {Entity}UpdatedEvent(Guid {Entity}Id) : IDomainEvent;
public sealed record {Entity}DeletedEvent(Guid {Entity}Id) : IDomainEvent;
EF Core Configuration
public sealed class {Entity}Configuration : IEntityTypeConfiguration<{Entity}>
{
public void Configure(EntityTypeBuilder<{Entity}> builder)
{
builder.ToTable("{entities}");
builder.HasKey(x => x.Id);
builder.Property(x => x.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(x => x.Price)
.HasPrecision(18, 2);
builder.Property(x => x.TenantId)
.IsRequired()
.HasMaxLength(64);
builder.HasIndex(x => x.TenantId);
builder.HasQueryFilter(x => x.DeletedAt == null);
}
}
Register in DbContext
public sealed class {Module}DbContext : DbContext
{
public DbSet<{Entity}> {Entities} => Set<{Entity}>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("{module}");
modelBuilder.ApplyConfigurationsFromAssembly(typeof({Module}DbContext).Assembly);
}
}
Add Migration
dotnet ef migrations add Add{Entity} \
--project src/Playground/FSH.Starter.Migrations.PostgreSQL \
--startup-project src/Playground/FSH.Starter.Api
dotnet ef database update \
--project src/Playground/FSH.Starter.Migrations.PostgreSQL \
--startup-project src/Playground/FSH.Starter.Api
Interfaces Reference
| Interface | Purpose | Auto-Handled |
|---|
IHasTenant | Tenant isolation | Query filtering |
IAuditableEntity | Created/Modified tracking | SaveChanges interceptor |
ISoftDeletable | Soft delete support | Delete interceptor |
AggregateRoot<T> | Domain events support | Event dispatcher |
Key Rules
- Private constructor - EF Core needs it, but users use factory methods
- Factory methods - All creation goes through
Create() static method
- Domain methods - State changes through methods, not property setters
- Domain events - Raise events for significant state changes
- Validation in methods - Validate in factory/domain methods, not entity
- No public setters - Properties are
private set
Checklist