Skip to content

Carrot.Data.Scaffold.Compilers.Modules.EntityFramework

net

Scaffold compiler module that generates the EF Core database access layer - DbContext, entities, repositories, queries, traits, enums, value conversions, and conventions - from the ScaffoldDatabase graph.

Installation

xml
<ProjectReference Include="..\Carrot.Data.Scaffold.Compilers.Modules.EntityFramework\Carrot.Data.Scaffold.Compilers.Modules.EntityFramework.csproj" />

Architecture

Compiler Module

ScaffoldCompilerEntityFramework extends ScaffoldCompilerModule<ScaffoldDatabase, ScaffoldCompilerEntityFrameworkOptions>. It iterates the database graph's contexts and registers renderers in two passes:

  1. Non-inherited contexts - DI installation/registration renderers
  2. All contexts (including inherited) - DbContext, DbSets, ModelBuilder, OnConfiguring, Register, Conventions, Conversions, Readme, plus per-entity (Entity, Repository, Queries), per-trait, and per-enum renderers

DbContext Generation

The DbContext renderer produces:

  • An I{ContextName} interface extending IDbContextBase with DatabaseName, ContextName, EntityNames[], and EntityTypes[] properties
  • A {ContextName} class extending DbContextBase<T> with two constructors (log-only and options+log)
  • A {ContextName}_ComparerFactory for creating ValueComparer<T> instances from IValueCompare<T> implementations

The ModelBuilder renderer configures entities via fluent API: table schemas, primary keys (with value generation), properties (required, max length, value comparers for custom conversions), navigations (HasOne/HasMany with foreign keys), and indexes (unique, clustered, method, operators).

Entity Generation

Each entity produces:

  • An I{EntityName} interface with properties and extends for modelling contracts, traits, and compound keys
  • An {EntityName} class implementing the interface
  • Property types are context-aware - enums and entity references resolve to their context-specific names

Repository Generation

Repositories support three key strategies:

  • CarrotId (default) - RepositoryBase<TRepo, TContext, TEntity> with EntityCarrotIdService
  • Compound keys - RepositoryBase<TRepo, TContext, TEntity, TKey> with EntityKeyServiceBase
  • Custom single-property keys - same compound key pattern but with a single non-CarrotId property

Each repository includes PrepareForInsert (sets CreatedOn), PrepareForUpdate (sets UpdatedOn), and PrepareForDelete (sets DeletedOn) lifecycle hooks.

Query Extension Generation

Per-entity query classes provide:

  • Include{Nav}/ThenInclude{Nav} methods for each navigation property
  • With{Nav}/For{Nav} filter methods for foreign key-based lookups (by key value or by entity instance)
  • Navigation shortcut properties - multi-hop navigation chains as computed extension properties
  • Navigation shortcut includes - multi-step Include().ThenInclude() chains

Trait Generation

Traits produce interfaces extending ITrait with the trait's declared properties.

EF Naming Extensions

Extensive extensions provide consistent EF naming throughout:

  • Entity names (GetEfName, GetEfVariable)
  • Property rendering with required/maxLength/comparer configuration
  • Navigation rendering with cardinality, foreign keys, and constraint names
  • Primary key rendering with composite key support
  • Index rendering with method, operators, and clustering options
  • Table schema rendering with custom table/schema names

Output Structure

Platform/Database/{Database}/
  Carrot.Platform.Database.{Database}.{Context}/
    {ContextName}.cs                    # DbContext + interface
    {ContextName}_DbSets.cs            # DbSet properties
    {ContextName}_ModelBuilder.cs       # OnModelCreating
    {ContextName}_OnConfiguring.cs      # Connection config
    {ContextName}_Register.cs           # DI registration
    {ContextName}_Conventions.cs        # Model conventions
    {ContextName}_Conversions.cs        # Value conversions
  Carrot.Platform.Database.{Database}.{Context}.Models/
    Entities/                           # Entity classes + interfaces
    Enums/                              # Enum types
    Traits/                             # Trait interfaces
    Repositories/                       # Repository classes + interfaces
    Queries/                            # Query extension classes

Dependencies

DependencyKind
Carrot.Data.Scaffold.Compilersproject (scaffold compiler framework)

Build

bash
dotnet build

Targets net10.0.


Usage Guide

Generate the EF Core database access layer (DbContext, entities, repositories, queries, traits, enums) from a scaffold definition.

Getting Started

Register the EntityFramework module on a scaffold compiler:

csharp
IScaffoldCompiler compiler = scaffold.CreateCompiler()
    .AddModuleEntityFramework();

Typically used alongside Api and Domain modules:

csharp
IScaffoldCompiler compiler = scaffold.CreateCompiler()
    .AddModuleApi()
    .AddModuleEntityFramework()
    .AddModuleDomain();

Then compile:

csharp
IScaffoldCompilerResult result = compiler.Compile();

Common Patterns

Generated DbContext

The generated context inherits from DbContextBase<T> and implements a scoped interface:

csharp
[ServiceScope(Scoped)]
public interface IPlatformContext : IDbContextBase
{
    string DatabaseName { get; }
    string ContextName { get; }
    string[] EntityNames { get; }
    Type[] EntityTypes { get; }
}

public partial class PlatformContext : DbContextBase<PlatformContext>, IPlatformContext
{
    public PlatformContext(ILog? log = null) : base(log) { }
    public PlatformContext(DbContextOptions<PlatformContext> options, ILog? log = null) : base(options, log) { }

    public string DatabaseName => "Platform";
    public string ContextName => "Platform";
    // ...
}

Generated Entity Structure

Each entity gets an interface and class:

csharp
public interface IUserEntity : IHasTimestamps, IUserTrait
{
    CarrotId Id { get; set; }
    string Name { get; set; }
    string Email { get; set; }
}

public class UserEntity : IUserEntity
{
    public CarrotId Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

Generated Repository

csharp
[ServiceScope(Scoped)]
public interface IUserRepository : IRepository<UserRepository, PlatformContext, UserEntity> { }

public class UserRepository : RepositoryBase<UserRepository, PlatformContext, UserEntity>, IUserRepository
{
    public UserRepository(ILog log, PlatformContext context, IUserEntityKeyService keyService)
        : base(log, context, keyService)
    {
        this.KeyService = keyService;
    }

    protected override RepositoryUpsertInsertMode DefaultUpsertInsertMode => RepositoryUpsertInsertMode.Throw;

    protected override void PrepareForInsert(UserEntity entity) { entity.CreatedOn = Dates.ComputerDateTimeUtc; }
    protected override void PrepareForUpdate(UserEntity entity) { entity.UpdatedOn = Dates.ComputerDateTimeUtc; }
    protected override void PrepareForDelete(UserEntity entity) { entity.DeletedOn = Dates.ComputerDateTimeUtc; }
}

Generated Query Extensions

csharp
public static class UserEntityQueries
{
    // Navigation includes
    public static IIncludableQueryable<UserEntity, OrganisationEntity> IncludeOrganisation(this IQueryable<UserEntity> query)
        => query.Include(x => x.Organisation);

    // Foreign key filters
    extension(IQueryable<UserEntity> query)
    {
        public IQueryable<UserEntity> ForOrganisation(CarrotId id)
            => query.Where(x => x.OrganisationId == id);

        public IQueryable<UserEntity> ForOrganisation(OrganisationEntity organisation)
            => query.Where(x => x.OrganisationId == organisation.Id);
    }
}

Tips

  • Repositories use lifecycle hooks - override PrepareForInsert, PrepareForUpdate, PrepareForDelete in your concrete repository to customize timestamp or audit behaviour.
  • Three key strategies are supported: CarrotId (default GUID-like), compound keys, and custom single-property keys (e.g. int, string). The generator picks the right one automatically.
  • Value conversions for custom types (e.g. Color, JsonDocument) are configured automatically when the type has a registered IValueCompare<T> in the database graph.
  • Query extensions are generated as static classes with extension methods, so they're discoverable via IntelliSense on IQueryable<TEntity>.
  • Traits are shared property interfaces (e.g. timestamps, soft delete) that entities implement - the generated entity class automatically inherits all trait properties.

Carrot