HomeNikola Knezevic

In this article

Banner

Complex Types in EF Core 10

08 Jan 2026
6 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

Modern applications need richer domain models than primitives like string and decimal.

We deal with addresses, money, date ranges and similar concepts every day. These values have meaning, structure and rules. Treating them as primitives leads to weaker models and more bugs over time.

This is where complex types in Entity Framework Core shine, especially in applications following DDD (Domain-Driven Design).

With EF Core 10, complex types take a major step forward and become far more practical for real-world systems.

Complex Types

If you’re new to the topic, complex types are essentially EF Core’s first-class support for value objects.

Value objects are a core DDD concept:

  • They are not entities
  • They don’t have identity
  • Equality is based on values
  • They represent a single concept, such as Money or Address

They are foundational for expressive domain models, however, for a long time EF Core didn’t support them cleanly.

For years, we used owned types to model value objects. While they solved mapping, they introduced trade-offs because owned types were still entity types.

Basically owned types solved mapping but not modeling.

With EF Core 8, complex types were introduced as true value objects.

Here’s a simple complex type example:

csharp
public class Order
{
    public Guid Id { get; set; }

    public Address ShippingAddress { get; set; }
}

public record Address(string Street, string City, string Country);

To configure it you can use ComplexProperty method:

csharp
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
    public void Configure(EntityTypeBuilder<Order> builder)
    {
        builder.ToTable(TableNames.Orders);

        builder.HasKey(order => order.Id);

        builder.ComplexProperty(i => i.ShippingAddress, address =>
        {
            address.Property(a => a.Street).HasMaxLength(200).IsRequired();
            address.Property(a => a.City).HasMaxLength(100).IsRequired();
            address.Property(a => a.Country).HasMaxLength(100).IsRequired();
        });
    }
}

If you prefer attributes, this also works:

csharp
[ComplexType]
public record Address(string Street, string City, string Country);

However, using complex types had a few limitations as well:

  • No optional complex types
  • No struct support
  • Limited JSON support
  • No collections

EF Core 10 addresses most of these gaps.

Optional Complex Types

In EF Core 10, complex types can be optional.

The entire complex object can be null. EF Core determines presence based on column values.

For example, you may want to have billing address optional if it matches shipping address:

csharp
public class Order
{
    public Guid Id { get; set; }

    public Address? BillingAddress { get; set; }

    public Address ShippingAddress { get; set; }

    public Money TotalAmount { get; set; }
}

This enables realistic domain models without workarounds.

Struct Support

EF Core 10 also adds struct and record struct support for complex types.

This is ideal for small, immutable value objects like Money:

csharp
public class Order
{
    public Guid Id { get; set; }

    public Address? BillingAddress { get; set; }

    public Address ShippingAddress { get; set; }

    public Money TotalAmount { get; set; }
}

public struct Money
{
    public required Currency Currency { get; set; }
    public required decimal Amount { get; set; }
}

This could reduce allocations, bring better performance and using records is a big plus for me since I prefer avoiding mutation of value objects.

JSON Mapping

Probably the most interesting update lies in new EF Core JSON mapping capabilities that are a great application for complex types.

Configuration is straightforward:

csharp
builder.ComplexProperty(i => i.BillingAddress, address => address.ToJson());

What makes this especially useful in EF Core 10 is that JSON columns are now fully queryable:

csharp
var ordersInParis = dbContext.Orders
    .Where(o => o.ShippingAddress.City == "Paris")
    .Select(o => new { o.Id, o.ShippingAddress.City })
    .ToListAsync(cancellationToken);

This is incredibly useful when:

  • Structures are deeply nested
  • Schema flexibility matters more than strict normalization

If you want to dive deeper, check out my dedicated post on EF Core 10 JSON mapping: JSON Type Support in EF Core 10

Breaking Changes

EF Core 10 introduces an important breaking change for complex types.

Previously, properties with the same name across different complex types could silently map to the same column.

Now column names are automatically uniquified, EF Core uses the full property path and suffixes are added when collisions occur.

This improves clarity, avoids bugs and makes large schemas much easier to reason about.

Conclusion

Complex types in EF Core 10 are no longer a niche feature.

They are now safer, more expressive and finally compatible with proper value object modeling.

But keep in mind, there are still limitations such as collections of complex types.

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.