Integration with Entity Framework Core
It is possible to use value objects (VOs) in EFCore. Using VO structs is straightforward, and no converter is required. Using VO classes requires generating a converter.
There are two ways of generating a converter. One is to generate it in the same project as the VO by adding the EFCoreValueConverter
conversion in the attribute, e.g.
Another way, if you're using .NET 8 or greater, is to use EfCoreConverter
attributes on a marker class, normally in a separate project from the value objects themselves:
The source generator augments this type with EFCore converters and comparers. This allows you to create these types in a separate project, which is useful if you're using something like Onion Architecture, where you don't want your domain objects to reference infrastructure code.
Now that the converters are generated by one of the approaches above, in your database context, you can now specify the conversion:
HasConversion
is an extension method that Vogen generates.
Another approach, if you're using .NET 8 or greater, is to override ConfigureConventions
RegisterAllInVogenEfCoreConverters
is a generated extension method. Note that the extension method's name after RegisterAllIn
relates to the name of marker class.
An EFCore example is included in the source.
Below is a walkthrough of that sample.
The sample uses EFCore to read and write entities to an in-memory database. The entities contain Value Objects for the fields and a Value Object for the primary key. It looks like this:
The individual Value Objects are:
The database context for this entity sets various things on the fields:
The HasVogenConversion
method is an extension method that is generated. It looks similar to this:
For the Id
field, because it's being used as a primary key, it needs to be a class because EFCore compares it to null to determine if it should be auto-generated. When it is null, EFCore will use the specified SomeIdValueGenerator
, which looks like:
There are things to consider when using Value Objects in EF Core
There are concurrency concerns with
ValueGenerator
if multiple threads are allowed to insert at the same time. It is not recommended to share the DB Context across threads. If concurrent creation is required, then a Guid is the preferred method of a primary, auto-generated key.There are a few hoops to jump though, especially for primary keys. Value Objects are primarily used to represent 'domain concepts,' and while they can be coerced into living in the 'infrastructure' layer, i.e., databases, they're not a natural fit. An alternative is to use an 'anti-corruption layer' to translate between the infrastructure and domain; it's a layer for converting/mapping/validation. Yes, it's more code, but it's explicit.