← Back to all posts

EF Core: Value Converters

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