HomeNikola Knezevic

In this article

Banner

Value Conversions in EF Core

09 Oct 2025
5 min

Special Thanks to Our Sponsors:

Sponsor Logo

EF Core is too slow? Discover how you can easily insert 14x faster (reducing saving time by 94%).

Boost your performance with our method within EF Core: Bulk Insert, update, delete and merge.

Thousands of satisfied customers have trusted our library since 2014.

👉 Learn more

Sponsor Newsletter

Entity Framework Core handles most data type conversions automatically, however, there are times when your entity properties don’t map directly to database column types.

For example, you might want to:

  • Store an enum as a string instead of an integer.
  • Wrap primitive keys in value objects to add additional level of type-safety.
  • Preserve DateTime.Kind.

This is where EF Core pulls out one of its handy tricks, Value Converters.

Value Converters

A Value Converter in EF Core is a component that transforms property values when reading from or writing to the database.

Value Conversions Diagram

This conversion can be from one value to another of the same type, for example encrypting strings or from a value of one type to a value of another type (enum to strings in database).

Value converters are specified in terms of a ModelClrType and a ProviderClrType. The model type is the .NET type of the property in the entity type. The provider type is the .NET type understood by the database provider.

Conversions are defined using two Func expression trees: one from ModelClrType to ProviderClrType and the other from ProviderClrType to ModelClrType.

This mechanism makes your entities more expressive while keeping persistence concerns separate.

Configuring a Value Converter

To get started, let’s set up a simple example and see how to configure a value converter:

csharp
public sealed class Order
{
    public Guid Id { get; set; }
    public OrderStatus Status { get; set; }
}
    
public enum OrderStatus
{  
    Submitted,
    Sent,
    Shipped,
    Canceled
}

Here we have an Order entity with a Status property of type OrderStatus. By default, EF Core will store enum values as integers in the database.

Let’s say we’d prefer to store them as readable strings like "Submitted", "Canceled", etc.

With this setup, EF Core will convert the enum to its string representation when saving to the database, and then parse it back to the correct enum value when reading it:

csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Order>()
        .Property(e => e.Status)
        .HasConversion(
            v => v.ToString(),
            v => (OrderStatus)Enum.Parse(typeof(OrderStatus), v));
}

Value converters like this can be configured inside OnModelCreating, and all we need to do is provide two expressions:

  • To Provider Expression: how to convert from the .NET type to the database type.
  • From Provider Expression: how to convert from the database type back to the .NET type.

Built-in Support

Luckily for us, EF Core has good support for conversion out of the box.

Pre-defined Conversions

Instead of us writing conversion functions manually, EF Core can pick the coversion based on the property type in the model and the requested database provider type.

For example, our example with enums EF Core actually can handle automatically when the provider type is configured as string using the generic type of HasConversion:

csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Order>()
        .Property(e => e.Status)
        .HasConversion<string>();
}

Additionally, EF Core can also automatically handle this case by explicitly specifying the database column type:

csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Order>()
        .Property(e => e.Status)
        .HasColumnType("nvarchar(20)");
}

Built-in Converters

EF Core also has a set of pre-defined ValueConverter classes:

csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var converter = new EnumToStringConverter<OrderStatus>();

    modelBuilder
        .Entity<Order>()
        .Property(e => e.Status)
        .HasConversion(converter);
}

In this example we created an instance of the built-in EnumToStringConverter<TEnum> to convert enum to string.

There are also a lot more built-in converters for enums, Guids, strings, IPAddresses and more.

NOTE: All built-in converters are stateless and a single instance can be safely shared by multiple properties.

Custom Converters

When built-ins aren’t enough, you can create a custom converter using ValueConverter<TModel, TProvider>.

For example, let's say we want strongly typed Ids:

csharp
public sealed record OrderId(Guid Value);

public sealed class Order
{
    public OrderId Id { get; set; }
    public OrderStatus Status { get; set; }
}

To create a custom converter is simple, it looks like this:

csharp
var stronglyTypedIdConverter = new ValueConverter<OrderId, Guid>(
    id => id.Value, 
    value => new OrderId(value)
);

modelBuilder.Entity<Order>()
    .Property(o => o.Id)
    .HasConversion(stronglyTypedIdConverter);

This ensures your domain stays strongly typed while the database stores a simple Guid.

Additionally, what you could do, in order to not repeat converters everywhere, you can also create a reusable generic converter:

csharp
public interface IStronglyTypedId
{
    Guid Value { get; }
}

public readonly record struct OrderId(Guid Value) : IStronglyTypedId;
public readonly record struct ProductId(Guid Value) : IStronglyTypedId;
csharp
public class StronglyTypedIdConverter<TStronglyTypedId> 
    : ValueConverter<TStronglyTypedId, Guid> where TStronglyTypedId : struct, IStronglyTypedId
{
    public StronglyTypedIdConverter()
        : base(
            id => id.Value,
            value => (TStronglyTypedId)Activator.CreateInstance(typeof(TStronglyTypedId), value)!)
    { }
}

Now, we can define sa many strongly typed Ids as we want while conversion logic lives in one place, reusable across our entire domain:

csharp
modelBuilder.Entity<User>()
    .Property(u => u.Id)
    .HasConversion(new StronglyTypedIdConverter<UserId>());

modelBuilder.Entity<Order>()
    .Property(o => o.Id)
    .HasConversion(new StronglyTypedIdConverter<OrderId>());

Known Limitations

Before you jump in and start utilizing value conversions there are few known limitations that you should be aware of:

  • Null cannot be converted.
  • You cannot spread a conversion of one property to multiple columns and vice-versa.
  • Parameters using value-converted types cannot currently be used in raw SQL APIs.

Conclusion

Value Converters in EF Core give you the flexibility to bridge the gap between your domain model and the database schema without compromising either.

And while EF Core already provides a great set of built-in converters for common cases, custom converters give you complete control for domain-specific needs.

Just keep in mind their few limitations, especially around nulls, multi-column mappings and raw SQL parameters.

If you want to check out examples I created, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.