Swevo.EFCore.StronglyTyped 1.0.1

Prefix Reserved
dotnet add package Swevo.EFCore.StronglyTyped --version 1.0.1
                    
NuGet\Install-Package Swevo.EFCore.StronglyTyped -Version 1.0.1
                    
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="Swevo.EFCore.StronglyTyped" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Swevo.EFCore.StronglyTyped" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Swevo.EFCore.StronglyTyped" />
                    
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 Swevo.EFCore.StronglyTyped --version 1.0.1
                    
#r "nuget: Swevo.EFCore.StronglyTyped, 1.0.1"
                    
#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 Swevo.EFCore.StronglyTyped@1.0.1
                    
#: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=Swevo.EFCore.StronglyTyped&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Swevo.EFCore.StronglyTyped&version=1.0.1
                    
Install as a Cake Tool

Swevo.EFCore.StronglyTyped

NuGet CI License: MIT

Compile-time strongly-typed ID generation for .NET using Roslyn source generators.

Add [StronglyTypedId] to any readonly partial struct and get a fully-featured value type with EF Core ValueConverter and System.Text.Json converter — all generated at build time. Zero reflection, AOT-safe, no runtime overhead.

[StronglyTypedId]
public readonly partial struct DeviceId;

// Generated for you:
//  ✔ Value property (Guid, int, long, or string)
//  ✔ Constructor, New(), Empty
//  ✔ IEquatable<T>, IComparable<T>
//  ✔ ==, !=, <, >, <=, >= operators
//  ✔ Explicit cast operators
//  ✔ DeviceId.EfCoreValueConverter (nested class)
//  ✔ DeviceId.SystemTextJsonConverter (nested class, auto-registered via [JsonConverter])

Why strongly-typed IDs?

Primitive obsession — using raw Guid, int, or string for IDs — is a silent source of bugs:

// Compiles fine. Swapped arguments cause a hard-to-find runtime bug.
void AssignDevice(Guid customerId, Guid deviceId) { ... }
AssignDevice(device.Id, customer.Id); // ← wrong order, no compiler error

Strongly-typed IDs turn these mistakes into compiler errors:

void AssignDevice(CustomerId customerId, DeviceId deviceId) { ... }
AssignDevice(device.Id, customer.Id); // ← CS1503: cannot convert DeviceId to CustomerId

Swevo.EFCore.StronglyTyped makes the pattern zero-friction — one attribute, everything else generated.


Installation

dotnet add package Swevo.EFCore.StronglyTyped

Requires .NET 6+ (or any target that supports netstandard2.0 packages).
EF Core is not a required dependency of the generator — the EfCoreValueConverter nested class is only needed when you use EF Core in your project.


Quick start

1. Declare your ID types

using Swevo.EFCore.StronglyTyped;

[StronglyTypedId]                       // default: Guid backing
public readonly partial struct DeviceId;

[StronglyTypedId(BackingType.Int)]
public readonly partial struct OrderId;

[StronglyTypedId(BackingType.Long)]
public readonly partial struct EventSequenceId;

[StronglyTypedId(BackingType.String)]
public readonly partial struct TagId;

2. Use the generated members

// Guid-backed — factory method and empty sentinel
var id     = DeviceId.New();          // new random DeviceId
var empty  = DeviceId.Empty;          // DeviceId wrapping Guid.Empty
var parsed = new DeviceId(someGuid);

// Equality and comparison work out of the box
id == DeviceId.New();   // false
id != empty;            // true
id.CompareTo(empty);    // non-zero

// Explicit casts (safe, no silent widening)
Guid raw = (Guid)id;
DeviceId back = (DeviceId)raw;

3. Wire up EF Core

Register the value converters on individual properties:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Device>()
        .Property(d => d.Id)
        .HasConversion(new DeviceId.EfCoreValueConverter());
}

Or apply all converters at once with a convention:

protected override void ConfigureConventions(ModelConfigurationBuilder builder)
{
    builder.Properties<DeviceId>().HaveConversion<DeviceId.EfCoreValueConverter>();
    builder.Properties<OrderId>().HaveConversion<OrderId.EfCoreValueConverter>();
}

4. JSON serialization

[JsonConverter] is applied automatically to every generated struct, so System.Text.Json serializes them transparently:

var device = new { Id = DeviceId.New(), Name = "Printer-01" };
var json   = JsonSerializer.Serialize(device);
// {"Id":"3fa85f64-5717-4562-b3fc-2c963f66afa6","Name":"Printer-01"}

var back = JsonSerializer.Deserialize<DeviceWrapper>(json);
// back.Id is a DeviceId, not a raw Guid

Backing types

BackingType C# type New() Empty JSON read JSON write
BackingType.Guid Guid reader.GetGuid() WriteStringValue()
BackingType.Int int reader.GetInt32() WriteNumberValue()
BackingType.Long long reader.GetInt64() WriteNumberValue()
BackingType.String string reader.GetString() WriteStringValue()

Generated API surface

For a [StronglyTypedId] struct named DeviceId with Guid backing, the generator emits:

[JsonConverter(typeof(DeviceId.SystemTextJsonConverter))]
public readonly partial struct DeviceId : IEquatable<DeviceId>, IComparable<DeviceId>
{
    public Guid Value { get; }
    public DeviceId(Guid value);

    public static DeviceId New();       // Guid only
    public static DeviceId Empty { get; } // Guid only

    public bool Equals(DeviceId other);
    public override bool Equals(object? obj);
    public override int GetHashCode();
    public override string ToString();
    public int CompareTo(DeviceId other);

    public static bool operator ==(DeviceId left, DeviceId right);
    public static bool operator !=(DeviceId left, DeviceId right);
    public static bool operator  <(DeviceId left, DeviceId right);
    public static bool operator  >(DeviceId left, DeviceId right);
    public static bool operator <=(DeviceId left, DeviceId right);
    public static bool operator >=(DeviceId left, DeviceId right);

    public static explicit operator Guid(DeviceId id);
    public static explicit operator DeviceId(Guid value);

    public sealed class EfCoreValueConverter
        : ValueConverter<DeviceId, Guid> { ... }

    public sealed class SystemTextJsonConverter
        : JsonConverter<DeviceId> { ... }
}

Diagnostics

ID Severity Description
STID001 Error [StronglyTypedId] applied to a struct that is not declared partial.

AOT / NativeAOT

Because all code is generated at build time and the EfCoreValueConverter uses compile-time lambda expressions, Swevo.EFCore.StronglyTyped is fully compatible with NativeAOT and Blazor WASM trimming.


The Swevo.EFCore.StronglyTyped package sits alongside the other compile-time generators in the Swevo suite:

Package Purpose
AutoWire Compile-time DI registration
AutoMap.Generator Compile-time object mapping
AutoValidate.Generator Compile-time FluentValidation wiring

License

MIT © Justin Bannister

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • 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
1.0.1 123 6/26/2026
1.0.0 99 6/26/2026

1.0.0: Initial release. Guid, int, long, and string backing types. EF Core ValueConverter and System.Text.Json converter generated automatically.