Table of Contents

Core Concepts

This article explains the architecture and key concepts of JD.Efcpt.Build.

Architecture Overview

JD.Efcpt.Build integrates into MSBuild by defining custom targets and tasks that run during the build process. The package consists of two main components:

  1. JD.Efcpt.Build - The NuGet package containing MSBuild targets and default configuration files
  2. JD.Efcpt.Build.Tasks - The .NET assembly containing MSBuild task implementations

When you add the package to your project, it hooks into the build pipeline and executes a series of stages to generate EF Core models.

The Build Pipeline

The pipeline consists of seven stages that run before C# compilation:

Stage 1: EfcptResolveInputs

Purpose: Discover the database source and locate all configuration files.

What it does:

  • Locates the SQL Project from project references or explicit configuration
  • Resolves the EF Core Power Tools configuration file (efcpt-config.json)
  • Finds renaming rules (efcpt.renaming.json)
  • Discovers T4 template directories
  • Resolves connection strings from various sources (explicit property, appsettings.json, app.config)

Outputs:

  • SqlProjPath - Path to the discovered SQL Project
  • ResolvedConfigPath - Path to the configuration file
  • ResolvedRenamingPath - Path to renaming rules
  • ResolvedTemplateDir - Path to templates
  • ResolvedConnectionString - Connection string (if using connection string mode)

Stage 2: EfcptEnsureDacpac / EfcptQuerySchemaMetadata

Purpose: Prepare the schema source for code generation.

DACPAC Mode (when using SQL Project):

  • Builds the SQL Project to produce a DACPAC file
  • Only rebuilds if source files are newer than the existing DACPAC
  • Uses msbuild.exe on Windows or dotnet msbuild on other platforms

Connection String Mode (when using a live database):

  • Connects to the database and queries system tables
  • Extracts table, column, index, and constraint metadata
  • Builds a canonical schema model for fingerprinting

Outputs:

  • DacpacPath - Path to the DACPAC file (DACPAC mode)
  • SchemaFingerprint - Hash of the database schema (connection string mode)

Stage 3: EfcptStageInputs

Purpose: Copy all inputs to a stable intermediate directory.

What it does:

  • Copies configuration files to obj/efcpt/
  • Stages T4 templates to obj/efcpt/Generated/CodeTemplates/
  • Normalizes paths for consistent fingerprinting

Outputs:

  • StagedConfigPath - Path to staged configuration
  • StagedRenamingPath - Path to staged renaming rules
  • StagedTemplateDir - Path to staged templates

Stage 4: EfcptApplyConfigOverrides

Purpose: Apply MSBuild property overrides to the staged configuration.

What it does:

  • Reads the staged efcpt-config.json file
  • Applies any non-empty MSBuild property overrides (37 properties across 5 sections)
  • Uses a typed model matching the complete efcpt-config.json schema
  • Writes the modified configuration back to the staged file

Override Sections:

  • Names - Namespace and DbContext naming
  • File Layout - Output paths and organization
  • Code Generation - 23 generation options
  • Type Mappings - DateOnly/TimeOnly, HierarchyId, spatial, NodaTime
  • Replacements - Custom naming with regex casing preservation

Override Behavior:

  • When using the library default config, overrides are always applied
  • When using a user-provided config, overrides are only applied if EfcptApplyMsBuildOverrides is true
  • Empty or whitespace-only values are treated as "no override"

Stage 5: EfcptComputeFingerprint

Purpose: Detect whether code regeneration is needed.

What it does:

  • Computes an XxHash64 (fast, non-cryptographic) hash of:
    • The DACPAC file contents (or schema fingerprint)
    • The staged configuration file
    • The staged renaming file
    • All files in the staged template directory
  • Compares with the previous fingerprint stored in obj/efcpt/fingerprint.txt

Outputs:

  • Fingerprint - The computed XxHash64 hash
  • HasChanged - Boolean indicating whether regeneration is needed

Stage 6: EfcptGenerateModels

Purpose: Run the EF Core Power Tools CLI to generate code.

What it does (only if HasChanged is true):

  • Locates the efcpt CLI using the configured tool mode
  • Executes efcpt with the DACPAC or connection string
  • Generates DbContext and entity classes
  • Renames generated files from .cs to .g.cs
  • Updates the fingerprint file

Tool Resolution Strategies:

  1. dnx (.NET 10+) - Executes via dotnet run without installation
  2. tool-manifest - Uses local tool manifest (.config/dotnet-tools.json)
  3. global - Uses globally installed tool
  4. explicit - Uses path specified in EfcptToolPath

Stage 7: EfcptAddToCompile

Purpose: Include generated files in compilation.

What it does:

  • Adds all .g.cs files from obj/efcpt/Generated/ to the Compile item group
  • Ensures generated code is compiled into your assembly

Fingerprinting

Fingerprinting is a key optimization that prevents unnecessary code regeneration. The system creates a unique hash based on all inputs that affect code generation.

What's Included in the Fingerprint

The fingerprint includes multiple sources to ensure regeneration when any relevant input changes:

  • Library version - Version of JD.Efcpt.Build.Tasks assembly
  • Tool version - EF Core Power Tools CLI version (EfcptToolVersion)
  • DACPAC content (in .sqlproj mode) or schema metadata (in connection string mode)
  • efcpt-config.json - Generation options, namespaces, table selection
  • MSBuild property overrides - All EfcptConfig* properties set in the .csproj
  • efcpt.renaming.json - Custom naming rules
  • T4 templates - All template files and their contents
  • Generated files (optional) - When EfcptDetectGeneratedFileChanges=true, includes fingerprints of generated .g.cs files

Important: The fingerprint is computed after MSBuild property overrides are applied, so changing any EfcptConfig* property (like EfcptConfigRootNamespace) will automatically trigger regeneration.

All hashing uses XxHash64, a fast non-cryptographic hash algorithm.

How Fingerprinting Works

Build 1 (first run):
  Fingerprint = Hash(library + tool + DACPAC/Schema + config + overrides + renaming + templates)
  → No previous fingerprint exists
  → Generate models
  → Store fingerprint

Build 2 (no changes):
  Fingerprint = Hash(library + tool + DACPAC/Schema + config + overrides + renaming + templates)
  → Same as stored fingerprint
  → Skip generation (fast build)

Build 3 (schema changed):
  Fingerprint = Hash(library + tool + new DACPAC/Schema + config + overrides + renaming + templates)
  → Different from stored fingerprint
  → Regenerate models
  → Store new fingerprint

Build 4 (config property changed):
  Fingerprint = Hash(library + tool + DACPAC/Schema + config + new overrides + renaming + templates)
  → Different from stored fingerprint (overrides changed)
  → Regenerate models
  → Store new fingerprint

Regeneration Triggers

The following changes will automatically trigger model regeneration:

  1. Library upgrade - When you update the JD.Efcpt.Build NuGet package
  2. Tool version change - When you change <EfcptToolVersion> in your .csproj
  3. Database schema change - Tables, columns, or relationships modified
  4. Config file change - efcpt-config.json or efcpt.renaming.json modified
  5. MSBuild property change - Any <EfcptConfig*> property changed in .csproj
  6. Template change - T4 template files added, removed, or modified
  7. Generated file change (optional) - When <EfcptDetectGeneratedFileChanges>true</EfcptDetectGeneratedFileChanges> is set

Detecting Manual Edits (Optional)

By default, the system does not detect changes to generated files. This prevents accidentally overwriting manual edits you might make to generated code.

To enable detection of changes to generated files (useful in some workflows):

<PropertyGroup>
  <EfcptDetectGeneratedFileChanges>true</EfcptDetectGeneratedFileChanges>
</PropertyGroup>

Warning: When enabled, any manual edits to .g.cs files will trigger regeneration, overwriting your changes. Only enable this if your workflow never involves manual edits to generated code.

Forcing Regeneration

To force regeneration regardless of fingerprint:

# Delete the intermediate directory
rmdir /s /q obj\efcpt

# Rebuild
dotnet build

Input Resolution

The package uses a multi-tier resolution strategy to find configuration files and database sources.

Resolution Priority

For each input type, the package searches in this order:

  1. Explicit MSBuild property - Highest priority
  2. Project directory - Files in the consuming project
  3. Solution directory - Files at the solution root
  4. Package defaults - Sensible defaults shipped with the package

Example: Configuration File Resolution

1. <EfcptConfig>custom-config.json</EfcptConfig>  → Use specified path
2. {ProjectDir}/efcpt-config.json                  → Use if exists
3. {SolutionDir}/efcpt-config.json                 → Use if exists
4. {PackageDir}/defaults/efcpt-config.json         → Use package default

SQL Project Discovery

The package discovers SQL Projects by:

  1. Checking EfcptSqlProj property (if set)
  2. Scanning ProjectReference items for SQL Projects
  3. Looking for SQL Projects in the solution directory
  4. Checking for modern SDK-style SQL Projects

Supported SQL Project SDKs:

SDK Extension Cross-Platform Notes
Microsoft.Build.Sql .sqlproj Yes Microsoft's official SDK-style SQL Projects for .NET
MSBuild.Sdk.SqlProj .csproj or .fsproj Yes Community SDK with additional features and extensibility
Traditional SQL Projects .sqlproj No (Windows only) Legacy format, requires SQL Server Data Tools

Key Differences:

  • Microsoft.Build.Sql uses the .sqlproj extension and is Microsoft's official modern SDK for SQL Projects in .NET SDK
  • MSBuild.Sdk.SqlProj uses .csproj or .fsproj extensions (despite having "SqlProj" in its name), provides more configurability and extensibility
  • Both SDK-style projects produce DACPACs that JD.Efcpt.Build uses for code generation

Generated File Naming

Generated files use the .g.cs suffix by convention:

  • ApplicationDbContext.g.cs - The generated DbContext
  • User.g.cs - Entity class for the Users table
  • Order.g.cs - Entity class for the Orders table

This convention:

  • Clearly identifies generated files
  • Prevents conflicts with hand-written code
  • Makes .gitignore patterns easy (*.g.cs)
  • Allows IDE tooling to recognize generated code

Schema-Based Organization

When use-schema-folders-preview is enabled, generated files are organized by database schema:

obj/efcpt/Generated/
├── ApplicationDbContext.g.cs
└── Models/
    ├── dbo/
    │   ├── User.g.cs
    │   └── Order.g.cs
    ├── sales/
    │   └── Customer.g.cs
    └── audit/
        └── Log.g.cs

With use-schema-namespaces-preview, entities also get schema-based namespaces:

namespace YourApp.Data.Entities.Dbo
{
    public class User { ... }
}

namespace YourApp.Data.Entities.Sales
{
    public class Customer { ... }
}

Tool Execution Modes

The RunEfcpt task supports multiple ways to locate and execute the EF Core Power Tools CLI:

dnx Mode (.NET 10+)

On .NET 10 and later, the tool is executed via dotnet run without requiring installation:

dotnet run --package ErikEJ.EFCorePowerTools.Cli --version 10.* -- efcpt ...

This is the default mode on .NET 10+ and requires no setup.

Tool Manifest Mode

Uses a local tool manifest (.config/dotnet-tools.json):

dotnet tool run efcpt ...

Enable with:

<EfcptToolMode>tool-manifest</EfcptToolMode>

Global Tool Mode

Uses a globally installed tool:

efcpt ...

This is the default mode on .NET 8 and 9.

Explicit Path Mode

Specify an exact path to the executable:

<EfcptToolPath>C:\tools\efcpt.exe</EfcptToolPath>

Next Steps