Trellis.EntityFrameworkCore
3.0.0-alpha.118
See the version list below for details.
dotnet add package Trellis.EntityFrameworkCore --version 3.0.0-alpha.118
NuGet\Install-Package Trellis.EntityFrameworkCore -Version 3.0.0-alpha.118
<PackageReference Include="Trellis.EntityFrameworkCore" Version="3.0.0-alpha.118" />
<PackageVersion Include="Trellis.EntityFrameworkCore" Version="3.0.0-alpha.118" />
<PackageReference Include="Trellis.EntityFrameworkCore" />
paket add Trellis.EntityFrameworkCore --version 3.0.0-alpha.118
#r "nuget: Trellis.EntityFrameworkCore, 3.0.0-alpha.118"
#:package Trellis.EntityFrameworkCore@3.0.0-alpha.118
#addin nuget:?package=Trellis.EntityFrameworkCore&version=3.0.0-alpha.118&prerelease
#tool nuget:?package=Trellis.EntityFrameworkCore&version=3.0.0-alpha.118&prerelease
EF Core Integration
Thin integration layer that eliminates repetitive EF Core boilerplate when using Trellis value objects and Result<T>.
Installation
dotnet add package Trellis.EntityFrameworkCore
Quick Start
Register all Trellis value objects as scalar properties with a single line in ConfigureConventions:
using Trellis.EntityFrameworkCore;
public class AppDbContext : DbContext
{
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
// Scans your assembly for CustomerId, OrderStatus, etc.
// Also auto-scans Trellis.Primitives for EmailAddress, Url, PhoneNumber, etc.
// Also auto-maps Money properties as owned types (Amount + Currency columns)
configurationBuilder.ApplyTrellisConventions(typeof(CustomerId).Assembly);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// No HasConversion() boilerplate needed — just configure keys, indexes, constraints
modelBuilder.Entity<Customer>(b =>
{
b.HasKey(c => c.Id);
b.Property(c => c.Name).HasMaxLength(100).IsRequired();
b.Property(c => c.Email).HasMaxLength(254).IsRequired();
});
}
}
Money Properties — Zero Configuration
Money properties are automatically mapped as owned types with proper column naming and precision.
No OwnsOne calls needed — just declare Money properties on your entities and they work.
This also applies when Money is declared on owned entity types, including items inside OwnsMany collections.
| Property Name | Amount Column | Currency Column | Amount Type | Currency Type |
|---|---|---|---|---|
Price |
Price |
PriceCurrency |
decimal(18,3) |
nvarchar(3) |
ShippingCost |
ShippingCost |
ShippingCostCurrency |
decimal(18,3) |
nvarchar(3) |
Maybe<T> Properties — Source Generator + Convention
Maybe<T> is a readonly struct that EF Core cannot map as optional. Use partial properties — the source generator and MaybeConvention handle everything:
public partial class Customer
{
public CustomerId Id { get; set; } = null!;
public partial Maybe<PhoneNumber> Phone { get; set; }
public partial Maybe<DateTime> SubmittedAt { get; set; }
}
No OnModelCreating configuration needed. Querying uses dedicated extensions:
var withoutPhone = await context.Customers.WhereNone(c => c.Phone).ToListAsync(ct);
var withPhone = await context.Customers.WhereHasValue(c => c.Phone).ToListAsync(ct);
var matches = await context.Customers.WhereEquals(c => c.Phone, phone).ToListAsync(ct);
var ordered = await context.Customers.WhereHasValue(c => c.Phone).OrderByMaybe(c => c.Phone).ToListAsync(ct);
Indexes, bulk updates, and diagnostics also stay strongly typed:
modelBuilder.Entity<Customer>(builder => builder.HasTrellisIndex(c => c.Phone));
await context.Customers
.Where(c => c.Id == customerId)
.ExecuteUpdateAsync(setters => setters.SetMaybeValue(c => c.Phone, phone), ct);
var mappings = context.GetMaybePropertyMappings();
var debugView = context.ToMaybeMappingDebugString();
Result-Returning SaveChanges
// Returns Result<int> instead of throwing on conflicts or FK violations
var result = await context.SaveChangesResultAsync(ct);
// Returns Result<Unit> when you don't need the count
var result = await context.SaveChangesResultUnitAsync(ct);
| Exception | Error Type |
|---|---|
DbUpdateConcurrencyException |
ConflictError |
| Duplicate key (unique constraint) | ConflictError |
| Foreign key violation | DomainError |
Query Extensions
// Maybe-returning queries (no exception on missing)
Maybe<Customer> customer = await context.Customers
.FirstOrDefaultMaybeAsync(c => c.Id == customerId, ct);
// Result-returning queries
Result<Customer> customer = await context.Customers
.FirstOrDefaultResultAsync(
c => c.Id == customerId,
Error.NotFound("Customer", customerId),
ct);
// Specification pattern
var activeSpec = new ActiveCustomerSpec();
var activeCustomers = await context.Customers
.Where(activeSpec)
.ToListAsync(ct);
Related Packages
- Trellis.Results — Core
Result<T>andMaybe<T>types - Trellis.Primitives — Value object base classes and built-in types
- Trellis.DomainDrivenDesign —
Specification<T>,Entity<T>,Aggregate<T>
License
MIT — see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.2)
- Trellis.Primitives (>= 3.0.0-alpha.118)
- Trellis.Results (>= 3.0.0-alpha.118)
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-alpha.137 | 21 | 3/27/2026 |
| 3.0.0-alpha.135 | 32 | 3/26/2026 |
| 3.0.0-alpha.127 | 37 | 3/23/2026 |
| 3.0.0-alpha.123 | 44 | 3/19/2026 |
| 3.0.0-alpha.118 | 49 | 3/14/2026 |
| 3.0.0-alpha.111 | 39 | 3/12/2026 |
| 3.0.0-alpha.104 | 37 | 3/9/2026 |
| 3.0.0-alpha.100 | 39 | 3/4/2026 |
| 3.0.0-alpha.99 | 38 | 3/4/2026 |
| 3.0.0-alpha.98 | 44 | 3/3/2026 |
| 3.0.0-alpha.95 | 48 | 3/2/2026 |
| 3.0.0-alpha.94 | 49 | 3/2/2026 |
| 3.0.0-alpha.93 | 42 | 3/1/2026 |
| 3.0.0-alpha.92 | 52 | 2/28/2026 |