100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

Value Conversions and Owned Types

Learn how EF Core value converters translate between C# and database types, and how owned types model value objects like Address without giving them independent identity.

ModelingAdvanced10 min readJul 10, 2026
Analogies

Why Value Conversions Exist

A value converter lets EF Core translate a .NET type your domain wants to use (like an enum, a custom Money struct, or a Uri) into a database-primitive type it can store (like a string or int), and back again when reading. You register one with .HasConversion(v => v.ToString(), v => Enum.Parse<OrderStatus>(v)) or the shorthand .HasConversion<string>() for enums, so your C# model stays expressive without forcing an ugly primitive-typed column onto your domain classes.

🏏

Cricket analogy: A value converter is like a translator converting a bowler's speed reading from km/h (the domain-friendly unit commentators use) into raw radar-gun mph data stored in the stadium's system, converting back and forth without either side needing to change.

Writing a Value Converter

For anything beyond simple enum-to-string mapping, you write a ValueConverter<TModel, TProvider> explicitly - for example, converting a Money value object into a decimal column, or a List<string> of tags into a comma-separated string with new ValueConverter<List<string>, string>(v => string.Join(',', v), v => v.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList()). EF Core applies the converter transparently on every read and write, so LINQ queries against the property still work as long as you're comparing against the converted (provider-side) value.

🏏

Cricket analogy: Writing a custom ValueConverter<Money, decimal> is like a scorer manually defining how to convert a complex 'overs bowled' figure (like 4.3 overs) into a single decimal balls-count for spreadsheet analysis, and back again for the printed scorecard.

Owned Types for Value Objects

An owned type - configured with [Owned] or modelBuilder.Entity<Order>().OwnsOne(o => o.ShippingAddress) - lets you model a value object like Address (with Street, City, PostalCode) as a genuine class with its own identity-free semantics, while EF Core maps its properties as columns on the owner's table (Order) by default, prefixed like ShippingAddress_City. Unlike a full entity, an owned type has no separate identity or DbSet - it only exists as part of its owner and is loaded and saved together with it.

🏏

Cricket analogy: An owned type like Address is like a player's 'batting stance details' (grip, guard position) that only make sense attached to that specific player's record - they're not tracked as an independent roster entry the way a full player entity would be.

Value converters and owned types solve different problems: use a converter when a single property needs a different database representation than its C# type; use an owned type when a group of properties together form a cohesive value object with its own behavior and no independent identity.

Owned Type Collections and Table Splitting

Owned types can also be collections (OwnsMany), which EF Core maps to a separate table with a shadow FK back to the owner (e.g., Order owning many OrderNote value objects), or you can force an owned reference type onto its own table with .ToTable("Addresses") instead of the default column-prefixed inline mapping - useful when the owned type has many properties and you want to avoid a wide, sparsely-populated owner table.

🏏

Cricket analogy: OwnsMany is like a player's record having a repeatable collection of 'injury history' entries stored in their own linked table, rather than cramming every injury into extra columns on the player's single row - similar to owning many OrderNote value objects.

csharp
public enum OrderStatus { Pending, Shipped, Delivered, Cancelled }

public class Order
{
    public int Id { get; set; }
    public OrderStatus Status { get; set; }
    public Address ShippingAddress { get; set; } = null!;
    public List<OrderNote> Notes { get; set; } = new();
}

public class Address
{
    public string Street { get; set; } = null!;
    public string City { get; set; } = null!;
    public string PostalCode { get; set; } = null!;
}

public class OrderNote
{
    public string Text { get; set; } = null!;
    public DateTime CreatedAt { get; set; }
}

modelBuilder.Entity<Order>(builder =>
{
    builder.Property(o => o.Status).HasConversion<string>();

    builder.OwnsOne(o => o.ShippingAddress, sa =>
    {
        sa.Property(a => a.PostalCode).HasMaxLength(20);
    });

    builder.OwnsMany(o => o.Notes, n =>
    {
        n.ToTable("OrderNotes");
        n.WithOwner().HasForeignKey("OrderId");
    });
});

Value converters that allocate or do non-trivial work on every read/write (like JSON serialization) can noticeably slow down large query result sets - benchmark before applying a converter broadly, and consider ValueComparer configuration for owned collection properties to avoid EF Core reporting false-positive change tracking.

  • Value converters translate between a C# type and a database-primitive type on every read and write.
  • HasConversion<string>() is the common shorthand for storing enums as readable text.
  • Custom ValueConverter<TModel, TProvider> instances handle more complex transformations like value objects or collections.
  • Owned types (OwnsOne/OwnsMany) model value objects with no independent identity or DbSet.
  • By default, an owned reference type's properties are mapped as prefixed columns on the owner's table.
  • OwnsMany maps to a separate table with a shadow FK back to the owner by default.

Practice what you learned

Was this page helpful?

Topics covered

#EntityFrameworkCoreStudyNotes#MicrosoftTechnologies#ValueConversionsAndOwnedTypes#Value#Conversions#Owned#Types#StudyNotes#SkillVeris#ExamPrep