# Add Entity

> Scaffold a new domain entity with value object ID, aggregate root, factory method, and domain event. Use whenever the user wants to add a new domain concept, database table, persisted object, or aggregate root to the system.

- Skill: `makigjuro/add-entity` (Agent Skill)
- Install (CLI): `npx skillmds@latest add makigjuro/add-entity`
- Raw SKILL.md: https://api.skillmd.com/api/skills/makigjuro/add-entity/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: makigjuro (https://skillmd.com/u/makigjuro)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/makigjuro/add-entity

---


# Add Domain Entity

When the user asks to add a new entity, scaffold following the project's domain-driven design patterns.

If the entity involves non-trivial EF Core configuration (owned types, complex value conversions, table splitting), use context7 to look up the current EF Core docs for the specific pattern before writing the configuration.

## Arguments

- `{Entity}` -- Entity name in PascalCase (e.g., `Customer`, `Order`)
- `{Service}` -- Target microservice that owns this entity. Ask if ambiguous.

## Configuration

Read `cloudstack.json` from the project root at the start of execution. Extract:
- `NAMESPACE` = `project.namespace` (default: detect from `*.sln` name or first `*.csproj` root namespace)
- `DOMAIN_PATH` = `backend.sharedDomainPath` (default: find directory matching `**/Domain/` containing `Entities/`)
- `INFRA_PATH` = `backend.sharedInfraPath` (default: find directory matching `**/Shared.*Infrastructure/`)
- `SERVICES` = `backend.services[]` (default: discover from `src/*/` directories containing `.Application/` subfolders)
- `SOLUTION` = `backend.solutionPath` (default: find `*.sln` in `src/`)

If `cloudstack.json` does not exist, auto-detect by scanning the project structure.

## Prerequisites

- The target microservice must exist under `src/` with Infrastructure and Host projects
- The shared Domain project must exist at `{DOMAIN_PATH}`
- EF Core tools must be installed (`dotnet tool list -g | grep ef`)

## 1. Value Object ID (`{DOMAIN_PATH}/ValueObjects/{Entity}Id.cs`)

```csharp
namespace {Namespace}.Domain.ValueObjects;

public class {Entity}Id : ValueObject
{
    public Guid Value { get; init; }

    public {Entity}Id(Guid value)
    {
        if (value == Guid.Empty)
            throw new ArgumentException("{Entity}Id cannot be empty.", nameof(value));
        Value = value;
    }

    public static {Entity}Id New() => new(Guid.NewGuid());

    protected override IEnumerable<object?> GetEqualityComponents()
    {
        yield return Value;
    }
}
```

## 2. Entity / Aggregate Root (`{DOMAIN_PATH}/Entities/{Entity}.cs`)

```csharp
namespace {Namespace}.Domain.Entities;

public class {Entity} : AggregateRoot<{Entity}Id>
{
    public TenantId TenantId { get; private set; }
    // Add properties with private setters

    // EF Core constructor
    protected {Entity}() : base(default!) { }

    // Private constructor called by factory method
    private {Entity}({Entity}Id id, TenantId tenantId, ...) : base(id)
    {
        TenantId = tenantId;
        // Set properties
    }

    // Factory method - the ONLY way to create instances
    public static {Entity} Create(TenantId tenantId, ...)
    {
        var id = {Entity}Id.New();
        var entity = new {Entity}(id, tenantId, ...);
        entity.RaiseDomainEvent(new {Entity}CreatedEvent(id, tenantId, DateTimeOffset.UtcNow));
        return entity;
    }

    // State-changing methods raise domain events
    public void Update(...)
    {
        // Modify state
        RaiseDomainEvent(new {Entity}UpdatedEvent(Id, ...));
    }
}
```

## 3. Domain Event (`{DOMAIN_PATH}/Events/{Entity}CreatedEvent.cs`)

```csharp
namespace {Namespace}.Domain.Events;

public class {Entity}CreatedEvent : DomainEvent
{
    public {Entity}Id {Entity}Id { get; }
    public TenantId TenantId { get; }
    public DateTimeOffset OccurredAt { get; }

    public {Entity}CreatedEvent({Entity}Id entityId, TenantId tenantId, DateTimeOffset occurredAt)
    {
        {Entity}Id = entityId;
        TenantId = tenantId;
        OccurredAt = occurredAt;
    }
}
```

## 4. Repository Interface (`Domain/` or `Application/`)

```csharp
public interface I{Entity}Repository
{
    Task<{Entity}?> GetByIdAsync({Entity}Id id, CancellationToken cancellationToken = default);
    Task AddAsync({Entity} entity, CancellationToken cancellationToken = default);
    Task SaveChangesAsync(CancellationToken cancellationToken = default);
}

public interface I{Entity}QueryRepository
{
    Task<{Entity}?> GetByIdAsync({Entity}Id id, CancellationToken cancellationToken = default);
    Task<(IReadOnlyCollection<{Entity}> Items, int TotalCount)> ListAsync(TenantId tenantId, int page, int pageSize, CancellationToken cancellationToken = default);
}
```

## 5. EF Core Configuration (`Infrastructure/Persistence/Configurations/{Entity}Configuration.cs`)

```csharp
public class {Entity}Configuration : IEntityTypeConfiguration<{Entity}>
{
    public void Configure(EntityTypeBuilder<{Entity}> builder)
    {
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Id)
            .HasConversion(id => id.Value, value => new {Entity}Id(value));
        builder.Property(x => x.TenantId)
            .HasConversion(id => id.Value, value => new TenantId(value));
        // Configure other properties
    }
}
```

## 6. Add DbSet to DbContext

```csharp
public DbSet<{Entity}> {Entity}s => Set<{Entity}>();
```

## 7. EF Core Migration (MANDATORY)

After adding the DbSet and entity configuration, **always** create a migration immediately:

```bash
dotnet ef migrations add Add{Entity} \
  --project src/{Service}/{Service}.Infrastructure \
  --startup-project src/{Service}/{Service}.Host
```

Review the generated migration to verify only expected changes are included. This step is **not optional** -- skipping it causes `PendingModelChangesWarning` errors at startup.

## Checklist
- [ ] Value object ID with validation and `GetEqualityComponents()`
- [ ] Entity inherits `AggregateRoot<TId>` with protected parameterless constructor
- [ ] Factory method is the only creation path
- [ ] Domain events raised in factory and state-change methods
- [ ] Past-tense event names: `{Entity}CreatedEvent`, `{Entity}UpdatedEvent`
- [ ] Separate command and query repository interfaces
- [ ] EF Core configuration with value object conversions
- [ ] DbSet added to DbContext
- [ ] **EF Core migration created** for the new table
- [ ] No framework dependencies in Domain layer

## Output

After scaffolding, report:
```
## Scaffolded: {Entity}

Files created/modified:
- `{DOMAIN_PATH}/ValueObjects/{Entity}Id.cs`
- `{DOMAIN_PATH}/Entities/{Entity}.cs`
- `{DOMAIN_PATH}/Events/{Entity}CreatedEvent.cs`
- `src/{Service}/{Service}.Application/` -- Repository interfaces
- `src/{Service}/{Service}.Infrastructure/Persistence/Configurations/{Entity}Configuration.cs`
- `src/{Service}/{Service}.Infrastructure/Persistence/{Service}DbContext.cs` (modified)
- Migration: `src/{Service}/{Service}.Infrastructure/Persistence/Migrations/{timestamp}_Add{Entity}.cs`

Next: Run `/add-command` or `/add-query` to create endpoints for this entity.
```

## Error Handling

- **DbContext not found:** Search for `*DbContext.cs` in the Infrastructure project. If none exists, the service may not have persistence set up yet -- warn the user.
- **EF Core tools not installed:** Run `dotnet tool install --global dotnet-ef` and retry.
- **Migration fails:** Check that the entity configuration is correct and the DbSet was added. Read the error output for hints.
- **Value object ID already exists:** The entity name may conflict with an existing one. Check `{DOMAIN_PATH}/ValueObjects/` before creating.

## Related Skills

- `/add-command` to create commands that operate on this entity
- `/add-query` to create queries that read this entity
- `/add-event-handler` to handle the domain events raised by this entity
- `/add-migration` to create the EF Core migration for the new table

