EF Core: Value Converters
Reading Time: 3 minutes
Problem
Database schemas and domain models often want different representations.
Examples:
- domain uses enums, database stores strings or ints
- domain uses value objects, database stores primitive columns
- domain uses
DateOnly, database provider expects another shape - sensitive values should be transformed before persistence
Without value converters, teams usually scatter conversion logic across services, repositories, and mapping layers. That causes:
- duplicated conversion code
- inconsistent read/write behavior
- harder testing and maintenance
- fragile refactoring when domain types evolve
Solution
Use EF Core Value Converters to define conversion rules once at the model level.
HasConversion lets you map between:
- model type (what your C# domain uses)
- provider type (what is stored in the database)
This keeps your domain expressive while keeping persistence predictable.
Common converter use cases:
- enum <-> string
- value object <-> scalar
- custom formatting (for example currency code normalization)
- basic reversible transformations for persistence workflows
Description
1) Enum to string conversion (.NET)
public enum OrderStatus
{
Pending,
Paid,
Cancelled
}
public class Order
{
public int Id { get; set; }
public OrderStatus Status { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.Property(o => o.Status)
.HasConversion<string>();
}
Benefits:
- readable data in the database
- safer against enum reordering issues compared with int storage
2) Value object conversion
public readonly record struct Money(decimal Amount)
{
public override string ToString() => Amount.ToString("0.00");
}
public class Product
{
public int Id { get; set; }
public Money Price { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property(p => p.Price)
.HasConversion(
v => v.Amount, // model -> provider
v => new Money(v)); // provider -> model
}
This lets your domain keep a strong type (Money) while persisting as decimal.
3) Reusable custom converter class
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
public sealed class UpperCaseTrimmedStringConverter : ValueConverter<string, string>
{
public UpperCaseTrimmedStringConverter()
: base(
v => string.IsNullOrWhiteSpace(v) ? string.Empty : v.Trim().ToUpperInvariant(),
v => v)
{
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>()
.Property(c => c.CountryCode)
.HasConversion(new UpperCaseTrimmedStringConverter())
.HasMaxLength(2);
}
Reusable converters help enforce consistency across entities.
4) Note on encryption scenarios
You can use converters for reversible transformations, but for strong security requirements, pair them with:
- proper key management (for example external key vault)
- rotation strategy
- deterministic vs random encryption decisions based on query needs
Avoid treating a simple converter as a full data-protection strategy by itself.
5) Good practices
- keep conversions deterministic and side-effect free
- avoid expensive conversion logic in hot paths
- test round-trip fidelity (model -> provider -> model)
- verify how converted columns interact with indexing and querying
Summary
EF Core value converters are a clean way to bridge domain modeling and relational storage.
They help you:
- centralize transformation logic
- reduce duplicated mapping code
- preserve expressive domain types
- keep persistence behavior consistent
If your domain model uses rich types, HasConversion is often one of the most practical tools to keep both domain and database design healthy.
References
- Microsoft Docs: Value Conversions
- Microsoft Docs: Entity Properties
- Microsoft Docs: Backing Fields and Encapsulation
- Microsoft Docs: EF Core Security Considerations