Skip to main content
Version: 0.2.x (0.2.1)

Use with Entity Framework Core

dotnet add package AdCodicem.ValueObjects.EntityFrameworkCore

Map every value object at once​

protected override void ConfigureConventions(ModelConfigurationBuilder builder)
=> builder.ConfigureValueObjects(typeof(Iban).Assembly);

One call maps every value object declared in the assembly to its underlying column type, with a value converter and a value comparer. It runs once, while the model is built; nothing happens per query or per row. Pass several assemblies if the value objects live in more than one.

Columns sized by the type​

A value object declaring MaxLength also sizes its column: an Iban with MaxLength = 34 becomes character varying(34) on PostgreSQL and nvarchar(34) on SQL Server, instead of unbounded text. A Guid value object lands in the provider's native GUID column. Change the rule on the type, and the next migration follows.

LINQ queries compare value objects as they would compare the underlying values: Where(a => a.Iban == iban) becomes an ordinary WHERE on the column.

Validation on read​

Materializing a row does not validate the value again: it uses CreateUnchecked. The read path is the hottest one in most applications, and it reads values this same application validated when it wrote them.

For a table another system also writes to, turn validation back on:

builder.ConfigureValueObjects(strict: true, typeof(Iban).Assembly);

It then costs one normalization and validation per materialized value.

One property, differently​

To depart from the convention for a single property:

modelBuilder.Entity<BankAccount>()
.Property(account => account.Iban)
.HasValueObjectConversion<Iban, string>(strict: true);

Prefer the convention everywhere else. And do not write HasConversion by hand for a value object: you would lose the generated comparer, and with it correct change tracking for a type whose comparison is not ordinal.

Value objects as keys​

A value object makes a perfectly good key, primary or foreign. For a string key declared with Comparison = StringComparison.OrdinalIgnoreCase, set a case-insensitive collation on the column, or the database and the application will disagree about which values are equal.

Public identifiers​

[EntityId] identifiers have a convention of their own, from AdCodicem.ValueObjects.Identifiers.EntityFrameworkCore, which maps them to fixed-width, non-Unicode columns. Call it in addition to ConfigureValueObjects; Public identifiers shows how.