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.