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:
- JD.Efcpt.Build - The NuGet package containing MSBuild targets and default configuration files
- 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 ProjectResolvedConfigPath- Path to the configuration fileResolvedRenamingPath- Path to renaming rulesResolvedTemplateDir- Path to templatesResolvedConnectionString- 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.exeon Windows ordotnet msbuildon 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 configurationStagedRenamingPath- Path to staged renaming rulesStagedTemplateDir- Path to staged templates
Stage 4: EfcptApplyConfigOverrides
Purpose: Apply MSBuild property overrides to the staged configuration.
What it does:
- Reads the staged
efcpt-config.jsonfile - 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
EfcptApplyMsBuildOverridesistrue - 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 hashHasChanged- 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
efcptCLI using the configured tool mode - Executes
efcptwith the DACPAC or connection string - Generates DbContext and entity classes
- Renames generated files from
.csto.g.cs - Updates the fingerprint file
Tool Resolution Strategies:
- dnx (.NET 10+) - Executes via
dotnet runwithout installation - tool-manifest - Uses local tool manifest (
.config/dotnet-tools.json) - global - Uses globally installed tool
- explicit - Uses path specified in
EfcptToolPath
Stage 7: EfcptAddToCompile
Purpose: Include generated files in compilation.
What it does:
- Adds all
.g.csfiles fromobj/efcpt/Generated/to theCompileitem 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.csfiles
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:
- Library upgrade - When you update the JD.Efcpt.Build NuGet package
- Tool version change - When you change
<EfcptToolVersion>in your .csproj - Database schema change - Tables, columns, or relationships modified
- Config file change - efcpt-config.json or efcpt.renaming.json modified
- MSBuild property change - Any
<EfcptConfig*>property changed in .csproj - Template change - T4 template files added, removed, or modified
- 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:
- Explicit MSBuild property - Highest priority
- Project directory - Files in the consuming project
- Solution directory - Files at the solution root
- 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:
- Checking
EfcptSqlProjproperty (if set) - Scanning
ProjectReferenceitems for SQL Projects - Looking for SQL Projects in the solution directory
- 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
.sqlprojextension and is Microsoft's official modern SDK for SQL Projects in .NET SDK - MSBuild.Sdk.SqlProj uses
.csprojor.fsprojextensions (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 DbContextUser.g.cs- Entity class for the Users tableOrder.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
- Configuration - Explore all MSBuild properties
- Connection String Mode - Use live database connections
- T4 Templates - Customize code generation