EricksonLopez.DomainPrimitives.Dapper.SourceGenerators 3.0.0

dotnet add package EricksonLopez.DomainPrimitives.Dapper.SourceGenerators --version 3.0.0
                    
NuGet\Install-Package EricksonLopez.DomainPrimitives.Dapper.SourceGenerators -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EricksonLopez.DomainPrimitives.Dapper.SourceGenerators" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EricksonLopez.DomainPrimitives.Dapper.SourceGenerators" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="EricksonLopez.DomainPrimitives.Dapper.SourceGenerators" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EricksonLopez.DomainPrimitives.Dapper.SourceGenerators --version 3.0.0
                    
#r "nuget: EricksonLopez.DomainPrimitives.Dapper.SourceGenerators, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EricksonLopez.DomainPrimitives.Dapper.SourceGenerators@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EricksonLopez.DomainPrimitives.Dapper.SourceGenerators&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=EricksonLopez.DomainPrimitives.Dapper.SourceGenerators&version=3.0.0
                    
Install as a Cake Tool

EricksonLopez.DomainPrimitives

Allocation-minimized, compile-time validated Domain Primitives, SmartEnums, and Roslyn Code Analyzers for modern .NET enterprise systems.

CI Coverage Quality Gate Mutation Score NuGet NuGet Downloads License: MIT .NET NativeAOT


EricksonLopez.DomainPrimitives is the enterprise standard for modeling provably valid, allocation-minimized scalar value types, strongly-typed identifiers, composite value objects, and SmartEnums in modern .NET (.NET 8, .NET 9, .NET 10). By combining compile-time Roslyn source generators, architectural code analyzers, and NativeAOT-first converters, it eliminates Primitive Obsession and defensive validation boilerplate while delivering bare-metal execution performance and allocation-minimized hot paths.


Table of Contents


🎯 What Problem It Solves

Primitive Obsession is among the most pervasive anti-patterns in enterprise software engineering:

  1. The Hidden Cost of Primitive Obsession: Using raw string, Guid, int, or decimal types allows illegal and unnormalized values (such as empty strings, malformed email addresses, or negative monetary balances) to traverse domain boundaries undetected. This forces developers to duplicate defensive validation logic across controllers, services, repositories, and UI layers.
  2. Heap Allocations & GC Overhead in Class-Based Wrappers: Traditional object-oriented Value Object implementations rely on class reference types. In high-throughput distributed systems, instantiating millions of transient identifier and scalar wrapper objects triggers intense Gen0/Gen1 heap churn, resulting in GC pauses and degraded P99 latencies.
  3. Runtime Reflection in ORMs, Serializers, and Mappers: Conventional value converters rely on runtime reflection (Activator.CreateInstance, MethodInfo.Invoke), inducing startup latency, degrading throughput, and breaking NativeAOT trimming optimization.
  4. Accidental Type Substitution & Invariant Drift: Passing raw scalar types into methods accepting multiple parameters of the same underlying type (e.g. TransferFunds(Guid sourceId, Guid targetId, decimal amount)) leads to catastrophic silent bugs that the compiler cannot detect.

How EricksonLopez.DomainPrimitives Solves This

  • Guaranteed Validity by Construction: Instances cannot be created in an invalid state. Constructors are private and creation is routed through source-generated Create, TryCreate, and TryParse methods that enforce validation rules deterministically.
  • Allocation-Minimized Hot Paths: Source-generated primitives are readonly partial record struct types that reside entirely on the stack or inline within entity memory layouts. Non-string primitives and StrongIds achieve 0 bytes allocated. String primitives with NFC Unicode normalization incur exactly 1 string allocation per value (required for homoglyph-attack prevention per SEC-004 / ADR-027).
  • Compile-Time Incremental Code Generation: All factory methods, parsers (IParsable<T>, ISpanParsable<T>, IUtf8SpanParsable<T>), formatters (ISpanFormattable, IUtf8SpanFormattable), equality operators, JSON converters, EF Core ValueConverters, and Dapper TypeHandlers are emitted at compile time.
  • Live IDE Architectural Enforcement: 18 dedicated Roslyn analyzers (DP0001–DP0018) intercept invalid modeling patterns, direct string comparisons, and public constructor bypasses in real time with automated code fixes.
  • Full NativeAOT & Trimming Compatibility: Zero runtime reflection and zero dynamic IL emission guarantee instant startup, minimal binary footprints, and full compatibility with NativeAOT publishing.

⚑ Key Features

  • πŸš€ Allocation-Minimized Memory Footprint: Stack-allocated readonly record struct value types achieve 0 B on non-string paths (StrongId, SmartEnum, NumericPrimitive). String primitives incur 1 allocation for NFC normalization. See Performance Benchmarks.
  • πŸ› οΈ Roslyn Incremental Source Generators: Compile-time emission of IParsable<T>, ISpanParsable<T>, IUtf8SpanParsable<T>, ISpanFormattable, and explicit conversion operators.
  • πŸ” Live Architectural Code Analyzers: 18 Roslyn diagnostic rules (DP0001–DP0018) with automated code fixes enforce immutability, validation integrity, and API surface budgets.
  • 🏷️ 39 Pre-Configured Semantic Shortcuts: Instant domain modeling with built-in attributes for strings ([Email], [Phone], [Url], [Slug], [CountryCode], [IBAN], [ISBN]) and numerics ([Money], [Price], [TaxRate], [Percentage], [Quantity], [Rating]).
  • 🧩 Zero-Contamination Persistence Adapters: Compile-time auto-discovery adapters for Entity Framework Core (ConfigureDomainPrimitives) and Dapper (RegisterAll).
  • 🌐 NativeAOT & Trimming-First Architecture: 100% trim-safe execution with zero reflection for all core and integration packages (see compatibility table). The legacy NewtonsoftJson package is excluded β€” see footnote ΒΉ.
  • 🎯 Railway-Oriented Result Pattern Interop: Seamless zero-overhead integration with EricksonLopez.Result and third-party functional monads via the TryCreate out parameter pattern.
  • πŸ§ͺ Comprehensive Testing & Data Tooling: Fluent assertions, scenario suites (DomainPrimitiveScenarios), and realistic fake data generators (DomainPrimitiveFakeFactory).

πŸ“¦ Ecosystem

Package Version Description
EricksonLopez.DomainPrimitives NuGet Core domain primitives, SmartEnums, attributes, and Roslyn generators
EricksonLopez.DomainPrimitives.Abstractions NuGet Zero-dependency contracts (IDomainPrimitive<TSelf, TValue>, IStrongId<TSelf, TValue>, PrimitiveError)
EricksonLopez.DomainPrimitives.AspNetCore NuGet ASP.NET Core Minimal APIs model binding & route parameter validation
EricksonLopez.DomainPrimitives.EFCore NuGet Entity Framework Core zero-contamination ValueConverter conventions
EricksonLopez.DomainPrimitives.Dapper NuGet Dapper compile-time type handlers and bulk auto-registration
EricksonLopez.DomainPrimitives.OpenApi NuGet Swagger / OpenAPI schema filter generators for primitive documentation
EricksonLopez.DomainPrimitives.Testing NuGet Fluent assertions, test builders, scenario data, and fake generators
EricksonLopez.DomainPrimitives.NewtonsoftJson NuGet Newtonsoft.Json contract resolvers and converters for legacy systems
EricksonLopez.DomainPrimitives.Analyzers NuGet Roslyn diagnostic analyzers (DP0001–DP0018) and automated code fixes
EricksonLopez.DomainPrimitives.Generators NuGet Roslyn incremental source generators emitting parsers, formatters, and factory methods
EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators NuGet Roslyn source generators emitting ASP.NET Core model binding and route parsers
EricksonLopez.DomainPrimitives.EFCore.SourceGenerators NuGet Roslyn source generators emitting EF Core ValueConverter configurations
EricksonLopez.DomainPrimitives.Dapper.SourceGenerators NuGet Roslyn source generators emitting Dapper TypeHandler registrations
EricksonLopez.DomainPrimitives.OpenApi.SourceGenerators NuGet Roslyn source generators emitting OpenAPI schema filters and descriptions

The ecosystem consists of 14 coordinated NuGet packages released in lockstep, including 6 compile-time Roslyn Source Generator and Analyzer packages (Generators, Analyzers, AspNetCore.SourceGenerators, EFCore.SourceGenerators, Dapper.SourceGenerators, OpenApi.SourceGenerators). See the Full Packages Specification for the complete dependency graph and target framework matrix.


πŸ“š Documentation

🌐 Official Documentation Hub: https://github.com/ericksonlopezf/dotnet-domain-primitives/tree/main/docs

πŸŽ“ Step-by-Step Interactive Showcase (Levels 00 to 10)

πŸ“– Showcase Catalog & Implementation Guide: docs/showcase/readme.md

Level Topic Showcase Projects Description
Level 00 Conceptual Architecture README.md Core foundational philosophy, advantages, trade-offs, and competitive comparisons
Level 01 Quick Start 01-GettingStarted Minimum configuration, first functional primitive ([Email], [StrongId<Guid>]), TryCreate
Level 02 Full Configuration 08-SerializationAndMapping, 09-SourceGenerators, 11-SmartEnums Assembly defaults ([assembly: DomainPrimitivesDefaults]), JSON serialization, Smart Enums
Level 03 Real Use Cases 04-ValueObjects, 05-StronglyTypedIds, 13-DomainCollections 38+ semantic shortcuts, composite Value Objects, collection extensions (ToDomainPrimitiveList)
Level 04 Advanced Integration 15-AspNetCoreIntegration, 16-EFCoreIntegration, 17-MediatRIntegration ASP.NET Core model binding, EF Core ConfigureDomainPrimitives(), MediatR CQRS pipelines
Level 05 Processing 21-BackgroundProcessing High-throughput Channel<T> producer/consumer with boundary reconstruction via TryCreate
Level 06 Error Handling 02-FirstResult, 03-Errors, 19-UnitTesting PrimitiveError struct, standardized error codes, Result pattern, fluent test builders
Level 07 Scalability & Performance 14-Performance Stack-allocated structs, zero GC allocations on success, span-based parsing (ISpanParsable<T>)
Level 08 Customization 22-CustomImplementations Custom validators (ICustomValidator<T>), normalizers (INormalizer<T>), PrimitiveBuilder
Level 09 Extensions 18-Observability, 23-DapperIntegration, 24-OpenApiIntegration OpenTelemetry metrics, static event sources, Dapper RegisterAll(), OpenAPI Swagger filters
Level 10 Enterprise Architecture 06-EntitiesAndAggregates, 07-DomainEvents, 12-Specifications, 20-EndToEndApplication Tactical DDD aggregates, domain events, specification pattern, full Clean Architecture

πŸ“– Technical Reference & Architecture Guides


πŸ“₯ Installation

Install the necessary packages using the .NET CLI or NuGet Package Manager:

1. Core Package (Required)

dotnet add package EricksonLopez.DomainPrimitives

2. Optional Framework & Integration Packages

# ASP.NET Core Minimal APIs & MVC model binding
dotnet add package EricksonLopez.DomainPrimitives.AspNetCore

# Entity Framework Core ValueConverter auto-configuration
dotnet add package EricksonLopez.DomainPrimitives.EFCore

# Dapper TypeHandler registration
dotnet add package EricksonLopez.DomainPrimitives.Dapper

# Swagger / OpenAPI Schema generation
dotnet add package EricksonLopez.DomainPrimitives.OpenApi

# Newtonsoft.Json legacy serialization support
dotnet add package EricksonLopez.DomainPrimitives.NewtonsoftJson

3. Testing & Assertion Packages

# Fluent assertions, fake data factories, and scenario runners
dotnet add package EricksonLopez.DomainPrimitives.Testing

πŸš€ Quick Start

1. Declarative Domain Primitive

Decorate a readonly partial record struct with semantic attributes. The source generator automatically emits parsers, formatters, validation pipelines, equality operators, and JSON converters.

using EricksonLopez.DomainPrimitives;

// String primitive with normalization and regex constraints
[StringPrimitive]
[Trim, UpperCase, Length(2, 2)]
public readonly partial record struct CountryIsoCode;

// Built-in shortcut for RFC 5321 compliant email addresses
[Email]
public readonly partial record struct EmailAddress;

// Usage:
CountryIsoCode code = CountryIsoCode.Create("  us  "); // Value: "US"
EmailAddress email = EmailAddress.Create("user@example.com"); // Validated & normalized

2. Strongly-Typed Identifier

Eliminate identifier transposition bugs by declaring strongly-typed IDs backed by Guid, long, int, or string:

using EricksonLopez.DomainPrimitives;

[StrongId<Guid>]
public readonly partial record struct CustomerId;

[StrongId<long>]
public readonly partial record struct OrderId;

// Usage:
CustomerId customerId = CustomerId.Create(); // Generates new Guid
OrderId orderId = OrderId.Create(1001L);      // Validated non-empty identifier

3. Type-Safe SmartEnum

Model exhaustive, polymorphic business states with $O(1)$ dictionary lookups and compile-time pattern matching:

using EricksonLopez.DomainPrimitives;

[SmartEnum<int>]
public readonly partial record struct OrderStatus
{
    public static readonly OrderStatus Pending = new(1);
    public static readonly OrderStatus Processing = new(2);
    public static readonly OrderStatus Shipped = new(3);
    public static readonly OrderStatus Delivered = new(4);
}

// Compile-time exhaustive pattern matching:
OrderStatus status = OrderStatus.Processing;
string description = status.Match(
    whenPending: () => "Awaiting payment",
    whenProcessing: () => "Fulfilling items in warehouse",
    whenShipped: () => "In transit with carrier",
    whenDelivered: () => "Successfully delivered");

4. Composite Value Object

Model multi-property domain concepts that enforce cross-property invariants via partial validation hooks:

using EricksonLopez.DomainPrimitives;
using EricksonLopez.DomainPrimitives.Validation;

[ValueObject]
public readonly partial record struct Address(string Street, string City, string ZipCode)
{
    static partial void Validate(ref Address value, ref PrimitiveError error)
    {
        if (string.IsNullOrWhiteSpace(value.Street))
            error = new PrimitiveError("Address.EmptyStreet", "Street cannot be empty.");
        else if (string.IsNullOrWhiteSpace(value.City))
            error = new PrimitiveError("Address.EmptyCity", "City cannot be empty.");
        else if (string.IsNullOrWhiteSpace(value.ZipCode))
            error = new PrimitiveError("Address.EmptyZipCode", "Zip code cannot be empty.");
    }
}

5. Zero-Allocation Validation & Result Pipeline

Execute high-throughput validation without throwing exceptions or incurring heap allocations:

using EricksonLopez.DomainPrimitives.Validation;

// Stack-allocated TryCreate with out PrimitiveError (0 bytes allocated)
if (EmailAddress.TryCreate(userInput, out var email, out PrimitiveError error))
{
    Console.WriteLine($"Valid email: {email.Value}");
}
else
{
    Console.WriteLine($"Validation failed [{error.Code}]: {error.Message}");
}

// High-performance UTF-8 byte span parsing (Zero string allocations)
ReadOnlySpan<byte> utf8Buffer = "alice@example.com"u8;
if (EmailAddress.TryParse(utf8Buffer, null, out var parsedEmail))
{
    Console.WriteLine($"Parsed from UTF-8 span: {parsedEmail}");
}

πŸ’‘ Core Use Cases

Use Case 1: Clean Architecture / CQRS Command Handler

Strongly-typed IDs and primitives guarantee invariant integrity before business logic executes in Application handlers:

using EricksonLopez.DomainPrimitives;

public readonly record struct RegisterCustomerCommand(
    CustomerId Id,
    EmailAddress Email,
    AccountBalance InitialDeposit);

public sealed class RegisterCustomerHandler
{
    private readonly ICustomerRepository _repository;

    public RegisterCustomerHandler(ICustomerRepository repository) => _repository = repository;

    public async Task<CustomerId> HandleAsync(RegisterCustomerCommand command, CancellationToken ct)
    {
        // Command parameters are guaranteed valid and non-null by the type system
        var customer = new Customer(command.Id, command.Email, command.InitialDeposit);
        await _repository.SaveAsync(customer, ct);
        return customer.Id;
    }
}

Use Case 2: Multi-Step Domain Validation Pipeline

Bridge TryCreate with EricksonLopez.Result for Railway-Oriented Programming without coupling domain types to external monad libraries:

using EricksonLopez.DomainPrimitives;
using EricksonLopez.Result;

public static class DomainResultBridge
{
    public static Result<EmailAddress> ToResult(string raw) =>
        EmailAddress.TryCreate(raw, out var email, out var error)
            ? Result<EmailAddress>.Success(email)
            : Result<EmailAddress>.Failure(error.Code, error.Message);
}

// Chained functional flow:
Result<CustomerProfile> profileResult = DomainResultBridge.ToResult(rawEmail)
    .Map(email => new CustomerProfile(email));

Use Case 3: Zero-Allocation Minimal API Route & Body Model Binding

Primitives automatically bind from route parameters, query strings, and JSON bodies via IParsable<T> and IUtf8SpanParsable<T>:

using EricksonLopez.DomainPrimitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

var app = WebApplication.Create();

// Automatically parsed from route via IParsable<CustomerId>
app.MapGet("/api/customers/{id}", (CustomerId id) => Results.Ok(new { Id = id.Value }));

// Automatically deserialized and validated from JSON body
app.MapPost("/api/customers", (CreateCustomerRequest request) =>
{
    // Properties are already strongly-typed primitives
    return Results.Created($"/api/customers/{request.Id}", request);
});

Use Case 4: EF Core Relational Persistence Mapping

Persist domain primitives into relational databases without contaminating domain models with persistence attributes:

using EricksonLopez.DomainPrimitives.EFCore.Generated;
using Microsoft.EntityFrameworkCore;

public sealed class ApplicationDbContext : DbContext
{
    public DbSet<Customer> Customers => Set<Customer>();

    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        // Automatically discovers and applies ValueConverters & column lengths for all primitives
        configurationBuilder.ConfigureDomainPrimitives();
    }
}

Use Case 5: High-Throughput Microservice Queries with Dapper

Execute high-performance database queries where Dapper maps database scalar columns directly into domain structs:

using Dapper;
using EricksonLopez.DomainPrimitives.Dapper.Generated;

// Startup registration (single call in Program.cs):
DapperDomainPrimitivesRegistration.RegisterAll();

// Queries materialize directly into domain types with zero reflection overhead:
var customer = await connection.QuerySingleAsync<Customer>(
    "SELECT Id, Email, Balance FROM Customers WHERE Id = @Id",
    new { Id = customerId });

Use Case 6: Live Compile-Time Roslyn Architectural Enforcement

Roslyn analyzers guard domain invariants at edit time inside the IDE, preventing common pitfalls before compilation:

// ❌ Roslyn Error DP0001: Domain primitive must be declared as 'partial'
[StringPrimitive]
public readonly record struct ApiKey; 

// ❌ Roslyn Error DP0002: Domain primitive must be declared as 'readonly'
[StringPrimitive]
public partial record struct SessionToken;

// ❌ Roslyn Warning DP0007: Avoid using default constructor for domain primitive
EmailAddress email = default; // Analyzer flags uninitialized state

πŸ”Œ Configuration & Integrations

ASP.NET Core Binding

Register model binding support in your ASP.NET Core application for MVC and Minimal APIs:

var builder = WebApplication.CreateBuilder(args);

// Register source-generated model binders
builder.Services.AddControllers()
    .AddDomainPrimitivesModelBinding();

OpenAPI / Swagger Schema Generation

Enable OpenAPI schema filters to document primitives accurately as primitive types (e.g. string format email) rather than complex objects:

builder.Services.AddSwaggerGen(options =>
{
    options.ConfigureDomainPrimitives();
});

Entity Framework Core Value Converters

Register all source-generated ValueConverter instances in one line using EF Core convention discovery:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.ConfigureDomainPrimitives();
}

Dapper Type Handlers

Register all source-generated Dapper SqlMapper.TypeHandler<T> instances at application startup:

using EricksonLopez.DomainPrimitives.Dapper.Generated;

// Program.cs
DapperDomainPrimitivesRegistration.RegisterAll();

System.Text.Json & NativeAOT

All primitives implement source-generated JSON converters that serialize directly to scalar JSON tokens (e.g. "user@example.com" instead of {"Value":"user@example.com"}). For NativeAOT, include your types in your JsonSerializerContext:

[JsonSerializable(typeof(CustomerDto))]
[JsonSerializable(typeof(EmailAddress))]
[JsonSerializable(typeof(CustomerId))]
public partial class AppJsonContext : JsonSerializerContext;

Newtonsoft.Json Migration Integration

For legacy applications using Newtonsoft.Json:

using EricksonLopez.DomainPrimitives.NewtonsoftJson;
using Newtonsoft.Json;

var settings = new JsonSerializerSettings();
settings.AddDomainPrimitives(); // Registers ContractResolver and converters

Roslyn Diagnostic Analyzers

The EricksonLopez.DomainPrimitives.Analyzers package provides 18 compile-time rules to enforce domain modeling invariants:

Diagnostic ID Severity Category Description CodeFix
DP0001 Error Correctness Domain primitive type must be declared as partial βœ… Available
DP0002 Error Correctness Domain primitive type must be declared as readonly βœ… Available
DP0003 Error Correctness Domain primitive type must be declared as record struct βœ… Available
DP0004 Error Correctness Invalid regular expression pattern in [Regex] attribute ❌ Manual
DP0005 Error Correctness Conflicting normalization attributes (e.g. [LowerCase] and [UpperCase]) βœ… Available
DP0006 Error Correctness Invalid constraint bounds (Min value cannot be greater than Max) ❌ Manual
DP0007 Warning Design Avoid uninitialized domain primitive via default constructor βœ… Available
DP0008 Error Correctness ValueObject properties must declare init accessors βœ… Available
DP0009 Warning Design Domain primitive lacks validation rules ❌ Manual
DP0010 Warning Performance Raw string compared directly with domain primitive using == βœ… Available
DP0011 Warning Performance string assigned directly from domain primitive without accessing .Value βœ… Available
DP0012 Warning Design Public constructor bypasses source-generated domain primitive validation βœ… Available
DP0013 Info Design Possible duplicate domain primitive logic detected ❌ Manual
DP0014 Warning ApiReview API surface budget exceeded on domain primitive ❌ Manual
DP0015 Warning ApiReview Public member on domain primitive is missing XML documentation ❌ Manual
DP0016 Warning ApiReview Custom factory method must be named Create, TryCreate, Parse, or TryParse ❌ Manual
DP0017 Error Correctness Invalid custom exception type in [DomainPrimitivesDefaults] ❌ Manual
DP0018 Warning Design Value object property should not be a mutable collection or array ❌ Manual

πŸ§ͺ Testing & Quality

Fluent Assertions API

The EricksonLopez.DomainPrimitives.Testing package provides declarative assertions for xUnit, NUnit, and MSTest:

using AwesomeAssertions;
using EricksonLopez.DomainPrimitives.Testing;
using Xunit;

public class DomainPrimitiveTests
{
    [Fact]
    public void EmailAddress_ValidInput_ShouldSucceed()
    {
        var email = DomainPrimitiveAssertionsExtensions
            .ShouldSucceedCreation<EmailAddress, string>("user@example.com");

        email.Should().HavePrimitiveValue<EmailAddress, string>("user@example.com");
    }

    [Fact]
    public void EmailAddress_InvalidInput_ShouldFailWithErrorCode()
    {
        DomainPrimitiveAssertionsExtensions
            .ShouldFailCreationWith<EmailAddress, string>("invalid-email", "FORMAT");
    }
}

Realistic Test Data Generation

Generate curated valid and invalid test datasets with DomainPrimitiveFakeFactory:

using EricksonLopez.DomainPrimitives.Testing;

// Valid and invalid sample datasets for parameterized tests
string[] validEmails   = DomainPrimitiveFakeFactory.Strings.ValidEmails;
string[] invalidEmails = DomainPrimitiveFakeFactory.Strings.InvalidEmails;
decimal[] validMoney   = DomainPrimitiveFakeFactory.Numerics.ValidMoneyAmounts;
int[] validAges        = DomainPrimitiveFakeFactory.Numerics.ValidAges;

// Grouped test scenarios
var scenarios = DomainPrimitiveScenarios.EmailNormalizationScenarios;
foreach (var (raw, expected) in scenarios)
{
    var created = EmailAddress.Create(raw);
    Assert.Equal(expected, created.Value);
}

Mutation Testing & Quality Gates

Every build is verified against a strict quality gate pipeline enforcing 100% mutant eradication:

Package Mutation Score Status
EricksonLopez.DomainPrimitives 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.Abstractions 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.Generators 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.Analyzers 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.AspNetCore 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.EFCore 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.EFCore.SourceGenerators 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.Dapper 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.Dapper.SourceGenerators 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.OpenApi 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.OpenApi.SourceGenerators 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.NewtonsoftJson 100.0% βœ… PASSED
EricksonLopez.DomainPrimitives.Testing 100.0% βœ… PASSED
Ecosystem Aggregate (14 packages) 100.0% βœ… VERIFIED

⚑ Performance Benchmarks

Environment: AMD Ryzen 7 9800X3D 4.70GHz (8 cores, 16 threads), .NET 10.0.10, X64 RyuJIT x86-64-v4, BenchmarkDotNet v0.15.8

Primary Operations Benchmark

Method Mean Ratio Allocated Zero-Alloc?
Raw Guid (baseline β€” no wrapper) 0.00 ns 1.00 0 B βœ…
[StrongId<Guid>] Creation (CustomerId.Create(guid)) 0.00 ns 1.00 0 B βœ…
[StrongId<Guid>] TryParse (CustomerId.TryParse(...)) 12.63 ns 1.00 0 B βœ…
[Email] Creation (EmailAddress.Create(...)) 49.53 ns - 48 B* ⚠️ (NFC Norm)
[SmartEnum] Lookup (OrderStatus.FromValue(2)) 2.14 ns - 0 B βœ…
[NumericPrimitive] Add (Money.Add(a, b)) 0.19 ns - 0 B βœ…

*Note: String normalization requires 1 allocation for string.Normalize(NormalizationForm.FormC) per Unicode security standards (SEC-004 / ADR-027).

BCL Span & UTF-8 Zero-Allocation Paths

Benchmark Interface Tested Mean Allocated Zero-Alloc?
DomainPrimitives_TryParse IParsable<T> 12.63 ns 0 B βœ…
DomainPrimitives_SpanParse ISpanParsable<T> 11.84 ns 0 B βœ…
DomainPrimitives_Utf8SpanParse IUtf8SpanParsable<T> 13.10 ns 0 B βœ…
DomainPrimitives_SpanFormat ISpanFormattable 4.82 ns 0 B βœ…
DomainPrimitives_Utf8SpanFormat IUtf8SpanFormattable 5.10 ns 0 B βœ…

Integration Overhead (EF Core & Dapper)

Benchmark Integration Layer Mean Allocated
Dapper_TypeHandler_SetValue Dapper Parameter Binding 0.21 ns 0 B
Dapper_TypeHandler_Parse Dapper Reader Materialization 0.19 ns 0 B
EFCore_ValueConverter_ConvertToProvider EF Core Write Pipeline 0.19 ns 0 B
EFCore_ValueConverter_ConvertFromProvider EF Core Read Pipeline 0.19 ns 0 B

🌐 Compatibility & Technical Matrix

Target Frameworks & NativeAOT

Package .NET 8.0 LTS .NET 9.0 STS .NET 10.0 NativeAOT Trimming Safe
EricksonLopez.DomainPrimitives βœ… Supported βœ… Supported βœ… Supported βœ… Supported βœ… 100% Trim-Safe
EricksonLopez.DomainPrimitives.Abstractions βœ… Supported βœ… Supported βœ… Supported βœ… Supported βœ… 100% Trim-Safe
EricksonLopez.DomainPrimitives.AspNetCore βœ… Supported βœ… Supported βœ… Supported βœ… Supported βœ… 100% Trim-Safe
EricksonLopez.DomainPrimitives.EFCore βœ… Supported βœ… Supported βœ… Supported βœ… Supported βœ… 100% Trim-Safe
EricksonLopez.DomainPrimitives.Dapper βœ… Supported βœ… Supported βœ… Supported βœ… Supported βœ… 100% Trim-Safe
EricksonLopez.DomainPrimitives.OpenApi βœ… Supported βœ… Supported βœ… Supported βœ… Supported βœ… 100% Trim-Safe
EricksonLopez.DomainPrimitives.Testing βœ… Supported βœ… Supported βœ… Supported βœ… Supported βœ… 100% Trim-Safe
EricksonLopez.DomainPrimitives.NewtonsoftJson βœ… Supported βœ… Supported βœ… Supported ❌ Not CompatibleΒΉ ❌ Not Trim-SafeΒΉ

ΒΉ EricksonLopez.DomainPrimitives.NewtonsoftJson is a legacy compatibility package targeting projects that cannot migrate to System.Text.Json. It uses Newtonsoft.Json's reflection-based pipeline ([RequiresDynamicCode] is applied on the converter) and is therefore not NativeAOT or trimming compatible (IsAotCompatible=false, IsTrimmable=false in the project file). For NativeAOT scenarios, use System.Text.Json with the source-generated converters bundled in EricksonLopez.DomainPrimitives instead. See ADR-026.

Primitive Category Taxonomy & Generated Interfaces

Category Decorator Attribute Underlying Type Key Generated Interfaces & Capabilities
Strong ID [StrongId<T>] Guid, long, int, string IDomainPrimitive<TSelf, TValue>, IStrongId<TSelf, TValue>, IParsable<T>, ISpanParsable<T>
String Primitive [StringPrimitive], [Email], [Phone], ... string IDomainPrimitive<TSelf, string>, ISpanParsable<T>, IUtf8SpanParsable<T>, ISpanFormattable
Numeric Primitive [NumericPrimitive<T>], [Money], [Price], ... decimal, double, int, long IDomainPrimitive<TSelf, T>, IComparable<T>, arithmetic operators (+, -, *, /)
Date Primitive [DatePrimitive] DateOnly, DateTime, DateTimeOffset IDomainPrimitive<TSelf, TDate>, IComparable<T>, past/future invariant guards
SmartEnum [SmartEnum<T>] int, string IDomainPrimitive<TSelf, T>, exhaustive Match<T>, Map<T>, $O(1)$ dictionary lookups
Value Object [ValueObject] Composite IDomainPrimitive<TSelf>, IParsable<T>, ISpanParsable<T>, structural equality

πŸ›‘οΈ Target Framework & Lifecycle Policy: First-class multi-targeting across .NET 10 (Modern LTS), .NET 9 (STS), and .NET 8 (Enterprise LTS) β€” along with .NET Standard 2.0 for Roslyn analyzers and source generators β€” is actively maintained. Full backward compatibility is guaranteed until Microsoft officially reaches End-of-Life (EOL) for .NET 8 and .NET 9 in November 2026, at which milestone the ecosystem will transition to .NET 10 and .NET 11.

πŸ›οΈ Architecture & Design Principles

End-to-End Architectural Pipeline

flowchart TD
    Client(["HTTP Client / Caller"])

    subgraph Presentation ["Presentation & Serialization Layer"]
        Json["System.Text.Json Converter\n(Auto-converts via TryCreate)"]
        OpenApi["OpenApi Schema Filter\n(Generates Swagger specs)"]
        AspNet["ASP.NET Core Model Binder\n(Route & Query Binding)"]
    end

    subgraph Domain ["Domain Layer (Zero Heap Allocation)"]
        Prim["Domain Primitive\n(readonly record struct)"]
        Pipeline["Validation Pipeline:\n1. Unicode Normalization (NFC)\n2. Built-in Range / Regex Rules\n3. Custom Partial Validator"]
    end

    subgraph Persistence ["Persistence Layer"]
        EF["EF Core ValueConverter\n(ConfigureDomainPrimitives)"]
        Dapper["Dapper TypeHandler\n(RegisterAll)"]
        DB[("Database")]
    end

    Client -->|"JSON Request Body"| Json
    Client -->|"Route / Query Parameter"| AspNet
    Client -->|"API Documentation"| OpenApi

    Json --> Prim
    AspNet --> Prim
    Prim --> Pipeline
    Pipeline -->|"Valid struct"| EF
    Pipeline -->|"Valid struct"| Dapper

    EF --> DB
    Dapper --> DB

Primitive Lifecycle & State Machine

stateDiagram-v2
    [*] --> RawInput: Caller invokes Create() or TryCreate()

    RawInput --> Normalizing: Has [Trim] / [LowerCase] / [UpperCase]
    RawInput --> Validating: No normalization

    Normalizing --> Validating: Normalized value

    Validating --> InvalidState: Built-in validation fails (LENGTH, REGEX, RANGE)
    Validating --> CustomValidating: Built-in validation passes

    CustomValidating --> InvalidState: Custom partial Validate() fails
    CustomValidating --> ValidState: All invariants satisfied

    InvalidState --> ThrowsException: Create() path -> Throws DomainPrimitiveValidationException
    InvalidState --> ReturnsFalse: TryCreate() path -> Returns false + PrimitiveError
    
    ThrowsException --> [*]
    ReturnsFalse --> [*]

    ValidState --> Instantiated: readonly record struct allocated on Stack
    Instantiated --> Serialized: System.Text.Json / Newtonsoft.Json
    Instantiated --> Persisted: EF Core / Dapper
    Instantiated --> [*]: Zero GC overhead (Stack released)

πŸ›‘οΈ Best Practices & Anti-Patterns

Scenario ❌ Avoid βœ… Recommended
Control Flow Throwing exceptions for business validation Using TryCreate with stack-allocated PrimitiveError
Memory Allocation Declaring domain primitives as class reference types Using readonly partial record struct for zero GC allocations
Struct Initialization Using default(Primitive) or parameterless new() Instantiating via source-generated Create() or TryCreate()
String Comparison Comparing raw string directly with a primitive (str == email) Parsing the raw string into the primitive or using email.Value
Value Object Mutation Modifying property values directly Creating a new instance with updated properties (immutable replacement)
Persistence Mapping Contaminating domain models with EF Core annotations Using zero-contamination ConfigureDomainPrimitives() in DbContext
Validation Architecture Running asynchronous I/O or DB queries inside primitive validators Keeping domain primitive validators 100% synchronous and deterministic

⚠️ Troubleshooting & Common Pitfalls

Always use generated factory methods (Create, TryCreate, TryParse) rather than default structs to ensure validation invariants are enforced.

1. Uninitialized Struct via default Constructor

  • Symptom: A domain primitive struct contains a null or uninitialized backing value, bypassing domain invariants.
  • Root Cause: C# allows struct initialization via default(T) or parameterless new T().
  • Solution & Roslyn Rule: Roslyn analyzer DP0007 warns against uninitialized primitives. Always use Primitive.Create(...) or Primitive.TryCreate(...).

2. Bypassing Validation via Public Constructors

  • Symptom: Developers instantiate primitives with raw data that violates regex, range, or length rules.
  • Root Cause: Declaring a custom public constructor overrides the source generator's controlled factory pattern.
  • Solution & Roslyn Rule: Roslyn analyzer DP0012 flags public constructors on primitives. Primitives must only be instantiated through generated factory methods.

3. Direct String Comparisons Bypassing Type Safety

  • Symptom: Comparing a domain primitive directly against a raw string (email == "admin@example.com") fails to normalize the input.
  • Root Cause: Direct string comparison bypasses trimming and casing rules emitted by the generator.
  • Solution & Roslyn Rule: Roslyn analyzers DP0010 and DP0011 flag direct string comparisons and assignments. Parse the raw string into the primitive first.

4. Mutating Value Objects Instead of Replacement

  • Symptom: Compile errors or invariant drift when attempting to mutate properties on a [ValueObject].
  • Root Cause: Value objects are immutable by design.
  • Solution & Roslyn Rule: Roslyn analyzer DP0008 enforces init accessors on all properties. Create new instances when updating values.

5. Missing partial or readonly Modifiers

  • Symptom: Compilation error stating the source generator cannot augment the type definition.
  • Root Cause: Source generators require partial to emit code and readonly record struct for immutability.
  • Solution & Roslyn Rule: Roslyn analyzers DP0001, DP0002, and DP0003 detect missing modifiers and provide one-click IDE CodeFixes.

🌐 Part of the EricksonLopez Ecosystem


🀝 Contributing

Contributions are welcome! Follow these steps to set up your local development environment:

Prerequisites

Development Workflow

  1. Clone the repository:
    git clone https://github.com/ericksonlopezf/dotnet-domain-primitives.git
    cd dotnet-domain-primitives
    
  2. Build the solution:
    dotnet build EricksonLopez.DomainPrimitives.slnx
    
  3. Execute unit & integration tests:
    dotnet test EricksonLopez.DomainPrimitives.slnx
    
  4. Run mutation testing quality gates:
    dotnet stryker
    

Please review our Contributing Guidelines and Code of Conduct before submitting pull requests.


πŸ“„ License

Distributed under the MIT License. Copyright Β© 2026 Erickson Lopez. All rights reserved.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0 36 9/20/2026
2.0.0 108 8/25/2026
1.0.0 109 8/11/2026