---
name: efcore-data-layer
description: This skill should be used by AI agents to assist with Entity Framework Core data layer development, including entity configuration, migrations, and database access patterns.
---

# Entity Framework Core Data Layer Skill

## Project Structure

- **DbContext**: `KdsAiDbContext` in `Kds.Ai.Data`
- **Entities**: `Kds.Ai.Data/Entities/`
- **Configurations**: `Kds.Ai.Data/Configurations/`
- **Migrations**: `Kds.Ai.Data/Migrations/`

## Entity Configuration

Use **Fluent API configuration** instead of data annotations. Create separate configuration classes implementing `IEntityTypeConfiguration<TEntity>`:

```csharp
public sealed class MonitorSpecConfiguration : IEntityTypeConfiguration<MonitorSpec>
{
    public void Configure(EntityTypeBuilder<MonitorSpec> builder)
    {
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Name).HasMaxLength(200).IsRequired();
        builder.HasIndex(x => x.RestaurantId);
        
        builder.HasMany(x => x.Runs)
            .WithOne(x => x.MonitorSpec)
            .HasForeignKey(x => x.MonitorSpecId);
    }
}
```

**Important**: Configurations are automatically discovered and applied via `ApplyConfigurationsFromAssembly` in `KdsAiDbContext.OnModelCreating`. Do NOT manually register configurations - just create the configuration class in the `Configurations/` folder and it will be auto-discovered.

## Entity ID Convention

Use **`Guid`** as primary key type for all entities:

```csharp
public sealed class MonitorSpec
{
    public Guid Id { get; set; }
    public Guid RestaurantId { get; set; }
    public string Name { get; set; } = string.Empty;
    // ...
}
```

Ids are set by the application, not generated by the database.

## Migration Commands

```bash
# Add new migration (run from /backend/Kds.Ai.Data directory)
cd ./backend/Kds.Ai.Data && ./Add-Migration.ps1 <MigrationName>

# Migrations are automatically applied at aspire host startup
```

## Best Practices

- Always use fluent configuration in `Configurations/` folder
- Never use `[Key]`, `[Required]`, `[MaxLength]` or other data annotations on entities
- Keep entities as POCOs without EF dependencies
- Use `sealed` classes for entities and configurations
- Configure indexes explicitly for foreign keys and frequently queried columns
- Use navigation properties with explicit foreign key configuration
- Enums are automatically converted to strings via `ConfigureConventions` in DbContext
- Configuration classes are auto-discovered - do NOT manually register them in OnModelCreating
- For explicit transactions under retrying providers, prefer `ExecuteInTransactionAsync` / `ExecuteInTransaction` on `CreateExecutionStrategy()` and pass input as state to avoid closures.
- When using `ExecuteInTransactionAsync`, it can return `TResult`, so use that result for atomic bulk operations instead of mutating outer variables.

## Repository Conventions

**IMPORTANT**: All repositories must live in the `Kds.Ai.Data` project, not in feature-specific projects.

- **Location**: `backend/Kds.Ai.Data/Repositories/`
- **Namespace**: `Kds.Ai.Data.Repositories`
- **Registration**: Register all repositories in `Kds.Ai.Data/DependencyInjectionExtensions.cs` using the `AddDataModule()` method
- **Naming**: 
  - Interface: `I{Entity}Repository` (e.g., `IMonitorSpecRepository`, `IMonitorRunRepository`)
  - Implementation: `Ef{Entity}Repository` (e.g., `EfMonitorSpecRepository`, `EfMonitorRunRepository`)
- **Access Modifier**: Implementation classes should be `internal sealed` (interfaces are `public`)

Example:
```csharp
// In Kds.Ai.Data/Repositories/IMonitorRunRepository.cs
namespace Kds.Ai.Data.Repositories;

public interface IMonitorRunRepository
{
    Task<MonitorRun?> GetByIdAsync(Guid id, CancellationToken ct = default);
    // ...
}

// In Kds.Ai.Data/Repositories/EfMonitorRunRepository.cs
namespace Kds.Ai.Data.Repositories;

internal sealed class EfMonitorRunRepository(KdsAiDbContext dbContext) : IMonitorRunRepository
{
    // Implementation
}

// In Kds.Ai.Data/DependencyInjectionExtensions.cs
builder.Services.AddScoped<IMonitorRunRepository, EfMonitorRunRepository>();
```

Feature-specific projects (like `Kds.Ai.AnomalyDetector`) should **only consume** repositories via dependency injection, never define them.

## Data Seeding Conventions

**IMPORTANT**: All data seeding logic must live in the `Kds.Ai.Data` project, not in feature-specific projects.

- **Location**: `backend/Kds.Ai.Data/Seeding/`
- **Namespace**: `Kds.Ai.Data.Seeding`
- **Registration**: Seeding is configured in `Kds.Ai.Data/DependencyInjectionExtensions.cs` using EF Core's `UseSeeding()` and `UseAsyncSeeding()` callbacks
- **Naming**: `{Entity}Seeding` for static seed classes (e.g., `MonitorSpecSeeding`)
- **Access Modifier**: Seed classes should be `public static` with `public static` methods

Example:
```csharp
// In Kds.Ai.Data/Seeding/MonitorSpecSeeding.cs
namespace Kds.Ai.Data.Seeding;

public static class MonitorSpecSeeding
{
    public static void Seed(KdsAiDbContext context)
    {
        SeedAsync(context, CancellationToken.None).GetAwaiter().GetResult();
    }

    public static async Task SeedAsync(KdsAiDbContext context, CancellationToken cancellationToken)
    {
        // Seeding logic
    }
}

// In Kds.Ai.Data/DependencyInjectionExtensions.cs
builder.Services.AddDbContext<KdsAiDbContext>(options =>
{
    options
        .UseSeeding((context, _) =>
        {
            if (context is KdsAiDbContext db)
                MonitorSpecSeeding.Seed(db);
        })
        .UseAsyncSeeding(async (context, _, cancellationToken) =>
        {
            if (context is KdsAiDbContext db)
                await MonitorSpecSeeding.SeedAsync(db, cancellationToken);
        });
});
```

Feature-specific projects should **never** define seeding logic. If you need seed data for a specific feature, add it to the appropriate seeding class in `Kds.Ai.Data/Seeding/`.
