Appearance
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:
- Non-inherited contexts - DI installation/registration renderers
- 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 extendingIDbContextBasewithDatabaseName,ContextName,EntityNames[], andEntityTypes[]properties - A
{ContextName}class extendingDbContextBase<T>with two constructors (log-only and options+log) - A
{ContextName}_ComparerFactoryfor creatingValueComparer<T>instances fromIValueCompare<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>withEntityCarrotIdService - Compound keys -
RepositoryBase<TRepo, TContext, TEntity, TKey>withEntityKeyServiceBase - 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 propertyWith{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 classesDependencies
| Dependency | Kind |
|---|---|
Carrot.Data.Scaffold.Compilers | project (scaffold compiler framework) |
Build
bash
dotnet buildTargets 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,PrepareForDeletein 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 registeredIValueCompare<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.