Skip to content

Carrot.Data.Scaffold.Process.Database

net

Database scaffolding process -- discovers entity definitions from an assembly and builds a complete database graph (entities, contexts, tables, navigations, keys, indices, conversions) for EF Core code generation.

Installation

Project reference:

xml
<ProjectReference Include="..\Carrot.Data.Scaffold.Process.Database\Carrot.Data.Scaffold.Process.Database.csproj" />

Architecture

Process & Concepts

ScaffoldDatabaseProcess extends ScaffoldProcess<ScaffoldDatabase, ScaffoldDatabaseOptions> and registers 7 concepts:

ConceptFlavorKeyGraph Type
DatabaseDeclarativeSingle(singleton)ScaffoldDatabaseGraphDatabase
ContextsDeclarativestringScaffoldDatabaseGraphContext
EntitiesIntrospectiveTypeScaffoldDatabaseGraphEntity
EnumsIntrospectiveTypeScaffoldDatabaseGraphEnum
AbstractIntrospectiveAbstractTypeScaffoldDatabaseGraphEntity
TraitsIntrospectiveTypeScaffoldDatabaseGraphTrait
TraitBehavioursDeclarativestringScaffoldDatabaseGraphTraitBehaviour

A built-in "Full" context (ScaffoldDatabaseDefineContextFull) is always registered.

Entity Configuration

The configure API mirrors EF Core conventions with expression-based property selectors:

  • HasPrimaryKey(x => x.Id) -- primary key with generation strategy
  • HasAlternateKey(x => x.Slug) -- unique alternate keys
  • HasIndex(x => x.Email) -- indices with uniqueness, filters
  • Property<T>(x => x.Name) -- per-property column config
  • NavigationTo<TTo>(x => x.Parent) -- FK navigations with cardinality, delete behavior
  • NavigationLinkTable<TA, TB>(x => x.Tags, x => x.Items) -- many-to-many link tables
  • ConfigureTable(t => t.Schema("dbo")) -- table naming, schema overrides

The navigation graph is rich, supporting:

  • Direct FK navigations with return navigations
  • Link table (many-to-many) navigations with both sides modeled
  • Navigation shortcuts -- multi-hop paths through the entity graph
  • Cardinality tracking (one-to-one, one-to-many, many-to-many)
  • Delete behavior configuration

Trait System

Traits define reusable property and navigation sets. ScaffoldDatabaseGraphTrait can carry properties, primary keys, navigations, and foreign keys that get applied to entities via trait behaviours.

Graph Builder & Provider Pipeline

Graph builders (ScaffoldDatabaseGraphEntityBuilder, etc.) handle graph construction. Providers (ScaffoldGraphEntityProvider, ScaffoldGraphTableProvider, etc.) handle the decomposition of entities into tables, columns, foreign keys, indices, etc.

Naming Policy

IScaffoldDatabaseNamingPolicy controls all generated database identifiers (table names, column names, FK names, index names, etc.). The default policy supports multiple casing strategies, truncation styles, and provider-specific naming.

Disclosure System

IScaffoldDatabaseDiscloseable -- entities, properties, contexts, and other graph items support a disclosure system for controlling visibility/accessibility in generated code.

Dependencies

  • Carrot.Data.Scaffold.Process -- core scaffolding framework
  • .NET 10

Build

bash
dotnet build

Usage Guide

Getting Started

csharp
using Carrot.Data.Scaffold;
using Carrot.Data.Scaffold.Process;

var process = new ScaffoldDatabaseProcess(
    typeof(MyAssemblyMarker).Assembly,
    new ScaffoldDatabaseOptions(),
    log);

ScaffoldDatabase result = process.Scaffold();
// result.Entities, result.Contexts, result.Database, etc.

Common Patterns

Defining a Database

csharp
public class MyDatabase : ScaffoldDatabaseDefineDatabase
{
    public override string DatabaseName => "MyPlatform";

    protected override void ScaffoldImpl(ScaffoldDatabaseConfigureDatabase config, ILog? log)
    {
        // Database-level configuration
    }
}

Defining a Context

csharp
public class TenantContext : ScaffoldDatabaseDefineContext
{
    public override string ContextName => "Tenant";

    public override void Scaffold(ScaffoldDatabaseConfigureContext config, ILog? log = null)
    {
        // Context-specific entity filtering
    }
}

Defining an Entity

csharp
public class UserScaffold : ScaffoldDatabaseDefineEntity<User>
{
    public override void Scaffold(
        ScaffoldDatabaseConfigureEntity<User> config,
        ScaffoldDatabaseReflectedEntity entity,
        ILog? log)
    {
        config.HasPrimaryKey(x => x.Id);

        config.HasAlternateKey(x => x.Email);

        config.HasIndex(x => x.CreatedAt);

        config.Property<string>(x => x.Email, p =>
        {
            // Column-level configuration
        });

        config.Property<string>(x => x.DisplayName);

        config.NavigationTo<Organisation>(x => x.Organisation);

        config.ConfigureTable(t =>
        {
            // Table schema, naming overrides
        });
    }
}
csharp
public class UserRoleScaffold : ScaffoldDatabaseDefineEntity<UserRole>
{
    public override void Scaffold(
        ScaffoldDatabaseConfigureEntity<UserRole> config,
        ScaffoldDatabaseReflectedEntity entity,
        ILog? log)
    {
        config.HasPrimaryKey(x => x.Id);

        config.NavigationLinkTable<User, Role>(
            x => x.User,
            x => x.Role);
    }
}

Abstract Base Entity

Apply shared configuration to all entities inheriting from a base type:

csharp
public class PlatformEntityScaffold : ScaffoldDatabaseDefineAbstract<PlatformEntity>
{
    public override void Scaffold(
        ScaffoldDatabaseConfigureEntity<PlatformEntity> config,
        ScaffoldDatabaseReflectedEntity entity,
        ILog? log)
    {
        config.HasPrimaryKey(x => x.Id);

        config.Property<DateTimeOffset>(x => x.CreatedAt);
        config.Property<DateTimeOffset>(x => x.UpdatedAt);
    }
}

Defining an Enum

csharp
public class UserStatusScaffold : ScaffoldDatabaseDefineEnum<UserStatus>
{
    public override void Scaffold(ScaffoldDatabaseConfigureEnum config, ILog? log = null)
    {
        // Enum values auto-discovered; configure overrides here
    }
}

Defining a Trait

Traits let you define reusable property/navigation sets applied across entities:

csharp
public class AuditableScaffold : ScaffoldDatabaseDefineTrait<IAuditable>
{
    public override void Scaffold(ScaffoldDatabaseConfigureTrait config, ILog? log = null)
    {
        config.Property<DateTimeOffset>("CreatedAt");
        config.Property<DateTimeOffset>("UpdatedAt");
        config.Property<string>("CreatedBy");
    }
}

Accessing Results

csharp
ScaffoldDatabase db = process.Scaffold();

// Iterate entities
foreach (var entity in db.Entities.Values)
{
    // entity.Properties, entity.Navigations, entity.PrimaryKey, etc.
}

// Get contexts (excluding the built-in "Full" context)
foreach (var context in db.GetContexts(includeFullContext: false))
{
    // context-specific entity sets
}

// Access the database singleton
var database = db.Database.Value;

Tips / Gotchas

  • A "Full" context is always auto-registered. Use GetContexts(includeFullContext: false) to exclude it when iterating.
  • RegisterAllConversions defaults to true in ScaffoldDatabaseOptions. Set to false if you only want explicitly defined conversions.
  • Expression selectors resolve property names from the lambda. Multi-property keys use params Expression[] overloads.
  • Abstract entity scaffolds run at priority 1 -- they execute before concrete entities, so base configuration is established first.
  • Navigation cardinality is inferred from the property type (single reference = one-to-one/many-to-one, collection = one-to-many).
  • The naming policy controls all identifiers. Swap IScaffoldDatabaseNamingPolicy to customize table names, FK conventions, index naming, etc.

Carrot