Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/purview-value-objects-ef-core-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"purview-value-objects": minor
---

feat: Entity Framework Core integration

- Reference `Microsoft.EntityFrameworkCore` and the generator emits an `EF` nested class per value object
(a `ValueConverter`/`ValueComparer`), an assembly-level `ValueObjectEFExtensions.ConfigureValueObjects`
extension for `OnModelCreating`, and a `UseValueObjects()` options-builder extension plus a generated
`ModelCustomizer` so contexts registered via `AddDbContext`, `AddDbContextFactory`, or `AddDbContextPool`
are mapped automatically without an `OnModelCreating` override.
- Scalar value objects convert to their underlying primitive column; complex value objects map as EF Core
complex types (EF Core 8+) by default or JSON columns via `[ValueObject(EFMapping = EntityFrameworkMapping.Json)]`.
- Queries compare the value object type directly — no `.Value` required.
- Value objects declared in **referenced assemblies** (e.g. a shared domain models project) are discovered
through their `IEFScalarValueObject`/`IEFComplexValueObject` marker interfaces and mapped by the consumer's
registry automatically, as long as the provider assembly references `Microsoft.EntityFrameworkCore`.
Provider-only assemblies can opt out of emitting their own registry with `DisableValueObjectsEFRegistry`.
- New options: `[Scalar(GenerateEFConverter, GenerateEFComparer)]`, `[ValueObject(EFMapping, GenerateEFComparer)]`,
and matching `[ValueObjectDefaults]` assembly defaults; `EntityFrameworkMapping` enum; `IEFScalarValueObject<,>` and
`IEFComplexValueObject<>` markers.
- Opt out via the `DisableValueObjectsEFGeneration` MSBuild property, `DisableValueObjectsEFRegistry`, per-assembly defaults, or per-type options.
- Diagnostics `VO1009` (EF requested without `Microsoft.EntityFrameworkCore`) and `VO1010` (auto-conversion
skipped for a non-mappable underlying type).
9 changes: 7 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<RoslynVersion>5.9.0</RoslynVersion>
<TUnitVersion>1.68.17</TUnitVersion>
<PurviewSGFVersion>1.0.0-prerelease.44</PurviewSGFVersion>
<PurviewZodSharpVersion>2.0.0-prerelease.12</PurviewZodSharpVersion>
<PurviewSGFVersion>1.0.0-prerelease.49</PurviewSGFVersion>
<PurviewZodSharpVersion>2.0.0-prerelease.20</PurviewZodSharpVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Purview.SourceGeneratorFramework" Version="$(PurviewSGFVersion)" />
Expand All @@ -22,7 +22,12 @@
</ItemGroup>
<ItemGroup Label="Testing">
<PackageVersion Include="TUnit" Version="$(TUnitVersion)" />
<PackageVersion Include="TUnit.Core" Version="$(TUnitVersion)" />
<PackageVersion Include="TUnit.Mocks" Version="$(TUnitVersion)" />
<PackageVersion Include="Bogus" Version="35.6.5" />
</ItemGroup>
<ItemGroup Label="Entity Framework (test/sample only)">
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.12" />
</ItemGroup>
</Project>
9 changes: 8 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ pipeline-tests *args:
echo "Running tests pipeline..."
"{{ pipeline_tool }}" --Build:RunTests=true --Release:Mode=None {{ args }}

# Run the pipeline through pack + validate (restore, build, lint, tests, pack, validate pack contents) without publishing/releasing
[group('Pipeline')]
pipeline-pack-validate *args:
just ensure-pipeline-tool
echo "Running pack + validate pipeline..."
"{{ pipeline_tool }}" --Build:RunPack=true --Build:ValidatePack=true --Release:Mode=None {{ args }}

# -----------------------------------------------------------------------------
# Build and Test
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -143,4 +150,4 @@ scrub:
find . -type d \( -name bin -o -name obj \) -exec rm -rf {} +
just clean
just restore --force-evaluate
dotnet build-server shutdown
dotnet build-server shutdown
9 changes: 0 additions & 9 deletions LICENSE.md

This file was deleted.

29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ incremental source generator produces:
**Use cases**

- **DTOs** – strong, self-validating types with serialization/deserialization and business rules.
- **Entity Framework** – value objects map cleanly onto JSON columns via `ScalarJsonConverterFactory`.
- **Entity Framework** – reference `Microsoft.EntityFrameworkCore` and the generator emits mapping members
(value converters, comparers, complex-type mapping) plus a `ConfigureValueObjects` extension for automatic
mapping. Queries use the value object type directly — no `.Value` required.
- **Domain models** – the F#-style single-case union pattern in C#.

## Install
Expand Down Expand Up @@ -75,6 +77,31 @@ modelBuilder
.HasColumnType("jsonb");
```

## Entity Framework Core

When your project references `Microsoft.EntityFrameworkCore`, the generator emits an `EF` nested class per value
object and an assembly-level `ConfigureValueObjects` extension that maps them automatically:

```csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ConfigureValueObjects(); // generated into your project
}
```

Scalar value objects convert to their underlying primitive column; complex value objects map as EF Core complex
types (EF Core 8+) by default or JSON columns via `[ValueObject(EFMapping = EntityFrameworkMapping.Json)]`. Queries compare
the value object type directly — no `.Value` required:

```csharp
EmailAddress email = "demo@example.com";
var customers = await db.Customers.Where(c => c.Email == email).ToListAsync();
var bigOrders = await db.Orders.Where(o => o.Total.Amount > 100m).ToListAsync();
```

See [Entity Framework](docs/Entity-Framework.md) for the full guide (automatic + manual mapping, assembly
defaults, and the three opt-out levels).

See the `src/src/Sample` and `src/src/ZodSharpSample` projects for end-to-end examples and `docs/` for guidance.

## Validation with ZodSharp
Expand Down
Loading
Loading