When I first started working seriously with Entity Framework Core, I treated it very simply. A string is a string, an int is an int, a DateTime is a DateTime. Life was easy. Then real business requirements arrived. Not tutorial requirements, but the ones coming from accounting, operations, or compliance teams. That is where things start to get interesting.
In this post, I want to talk about value conversions in EF Core. Not in a textbook way, but from how I actually used them in projects. I will share the mistakes I made, what confused me at first, and how value converters helped me keep my domain clean without fighting SQL.
All example code in this blog is not taken from my actual production projects. The code is intentionally simplified and recreated only to demonstrate the idea clearly.
Why value conversions even matter in real projects
In one of my projects, we were building a financial system. The database already existed and it was not designed by developers. Columns were strings, integers, sometimes even char fields with magic values. Meanwhile, on the application side, we wanted something cleaner.
I did not want this everywhere in my code:
if (order.Status == "P")
{
// Pending
}This kind of code spreads like a virus. After a few months, no one remembers what “P” means. New developers are scared to touch it. Even you, after six months, will forget.
What I wanted instead was something like:
if (order.Status == OrderStatus.Pending)
{
// Pending
}The problem was simple. SQL stored a string. C# wanted an enum or a custom type. That is exactly where value conversions come in.
What is a value conversion in EF Core
In simple words, a value conversion tells EF Core how to translate a property in your entity to a value that can be stored in the database, and how to translate it back when reading.
Think of it as a translator sitting between your domain model and the database.
In EF Core, this is done using ValueConverter.
At first, I thought value converters were some advanced feature only used in rare cases. Later I realized they are very practical and solve very common problems.
Starting with a simple example
Let us start small.
Imagine an Order entity.
public class Order
{
public int Id { get; set; }
public OrderStatus Status { get; set; }
}And the enum:
public enum OrderStatus
{
Pending = 1,
Paid = 2,
Cancelled = 3
}The database column already exists and looks like this:
Status varchar(10)And the values stored are:
- PENDING
- PAID
- CANCELLED
By default, EF Core does not know how to map OrderStatus to that string. This is where we configure a value conversion.
Configuring a value converter
Inside OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.Property(o => o.Status)
.HasConversion(
v => v.ToString(),
v => (OrderStatus)Enum.Parse(typeof(OrderStatus), v)
);
}This code says:
- When saving, convert the enum to string
- When reading, parse the string back to enum
The first time I wrote this, I felt a small relief. The domain model stayed clean. The database stayed untouched. Everyone was happy.
Using it in queries without thinking about SQL
One thing I really like about value converters is how transparent they are.
You can write:
var paidOrders = dbContext.Orders
.Where(o => o.Status == OrderStatus.Paid)
.ToList();EF Core translates this into SQL using the converted value. You do not have to manually write “PAID” anywhere. This is where it starts to feel powerful. You think in business terms, not database terms.
When enums are not enough
Enums are nice, but real projects often need more than that. In one project, we had a Money type. It was not just a decimal. It had currency, rounding rules, and some validations.
Something like this:
public class Money
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
}The database had two columns:
Amount decimal(18,2)
Currency char(3)I remember struggling here. At first, I tried mapping it manually everywhere. Projections became messy. Joins became ugly. Then I realized value converters can also work with complex types, as long as you store them as a single value.
So we changed the approach.
Storing a custom type as a single column
For demonstration, let us simplify Money and store it as a string like “100.50|PHP”.
public class Invoice
{
public int Id { get; set; }
public Money Total { get; set; }
}Value converter:
var moneyConverter = new ValueConverter<Money, string>(
v => $"{v.Amount}|{v.Currency}",
v =>
{
var parts = v.Split('|');
return new Money(decimal.Parse(parts[0]), parts[1]);
}
);
modelBuilder.Entity<Invoice>()
.Property(i => i.Total)
.HasConversion(moneyConverter);- Is this perfect? No.
- Is this sometimes useful? Yes.
In one of my projects, this approach helped us move fast while keeping domain logic clean. Later, when the database design improved, we refactored it.
Filtering and querying with converted values
This is where you need to be careful.
You can do this:
var invoices = dbContext.Invoices
.Where(i => i.Total.Currency == "PHP")
.ToList();This will not work. EF Core cannot translate that because the conversion happens at the property level.
What you can do instead is design helper properties or rethink how the value is stored. In real projects, this often leads to splitting columns instead of forcing everything into one.
Value converters are powerful, but they are not magic.
Value conversions with joins and projections
In another project, we had audit logs. The database stored event types as integers. The application wanted a strong type.
public enum AuditEventType
{
Created = 1,
Updated = 2,
Deleted = 3
}Entity:
public class AuditLog
{
public int Id { get; set; }
public AuditEventType EventType { get; set; }
public DateTime CreatedAt { get; set; }
}When projecting data:
var logs = dbContext.AuditLogs
.Select(l => new
{
l.Id,
l.EventType,
l.CreatedAt
})
.ToList();The projection works fine. The conversion is applied automatically.
This made reporting code much cleaner. Instead of mapping magic numbers in the UI, everything was already meaningful.
Lessons I learned the hard way
Let me be honest. I made mistakes with value converters.
- Lesson one: Do not put heavy logic inside converters. They should be simple and predictable.
- Lesson two: Think about querying early. If you need to filter by parts of the value, a single column conversion might hurt you later.
- Lesson three: Value converters are not a replacement for good database design. They are a bridge, not an excuse.
When value conversions really shine
From my experience, value conversions are perfect for:
- Enums stored as strings or integers
- Strongly typed identifiers
- Simple value objects
- Legacy database integration
- Keeping domain models expressive
They helped me explain the system better to business people too. Instead of saying “status P”, I could say “Pending”. That matters more than we think.
Final thoughts
If you are early in your EF Core journey, value conversions might feel optional. Once you work on systems that live for years, with changing requirements and different teams, they become very valuable.
I remember struggling with ugly mappings, duplicated logic, and unclear meaning in code. Value converters were not the only solution, but they were a big step toward cleaner and more maintainable systems.
If you take one thing from this post, let it be this. Model your domain the way you understand the business, then use EF Core features to adapt to the database, not the other way around.






