Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Performance regression with PrimitiveCollections using a complex ValueConverter #36856

@desruc

Description

@desruc

Bug description

We have found that query performance degrades dramatically when using PrimitiveCollections with a complex ValueConverter. What takes 10 seconds under EF8 now takes over a minute in EF10.

This is a minimal reproduction that mimics our production code - we're seeing this same performance degradation in our real application, making it impossible to upgrade EF versions.

The Scenario

We were able to reproduce the issue in a minimal project: https://github.com/desruc/ef-valueconverter-performance-issue

Here's an overview of the reproduction and test results:

The Pattern

We use value objects in our domain, like this:

public class TagId
{
    public string Value { get; }
    public TagId(string value) => Value = value;
}

These value objects get stored in primitive collections on owned entities:

public class Child // Owned entity
{
    public string ChildId { get; set; }
    public List<TagId> TagIds { get; set; }
}

public class Parent
{
    public string Id { get; set; }
    public IReadOnlyList<Child> Children { get; set; } // Owns many Children
}

To make EF Core work, we use a generic ValueConverter:

public class ReflectionValueConverter<T> : ValueConverter<T, string>
{
    public ReflectionValueConverter() : base(
        convertToProviderExpression: v => ((TagId)(object)v).Value,

        convertFromProviderExpression: v => (T)typeof(T)
            .GetConstructor(new[] { typeof(string) })!
            .Invoke(new object[] { v })
    )
    {
    }
}

The EF configuration that triggers the issue:

builder.OwnsMany<Child>(x => x.Children, b =>
{
    // The performance killer is this line:
    b.PrimitiveCollection(p => p.TagIds)
        .ElementType()
        .HasConversion<ReflectionValueConverter<TagId>>();
});

Test Data

Our reproduction creates:

  • 1,000 parent records
  • Each parent has 100 children
  • Each child has 100 TagId objects
  • Total: 10 million primitive collection items

The Query

We run a simple query that loads everything:

await context.Parents.AsSplitQuery().ToListAsync();

Performance Results

EF Version Time
EF8 (8.0.20) 10.8 seconds
EF9 (9.0.9) 22.8 seconds
EF10 (10.0.0-rc.1.25451.107) 1 minute 20.3 seconds

Note: When we tested the same scenario using List<string> instead of List<TagId> (no ValueConverter), all EF versions performed consistently fast. This confirms the regression is specifically related to ValueConverter processing, not PrimitiveCollections in general.

Additional Observation

The performance problem largely goes away when the expression is extracted to a static method:

public class ReflectionValueConverter<T> : ValueConverter<T, string>
{
    public ReflectionValueConverter() : base(
        convertToProviderExpression: v => ((TagId)(object)v).Value,
        convertFromProviderExpression: v => ConvertFromString(v)
    )
    {
    }

    static T ConvertFromString(string v) => (T)typeof(T)
        .GetConstructor(new[] { typeof(string) })!
        .Invoke(new object[] { v });
}
EF Version Time
EF8 (8.0.20) 5.7 seconds
EF9 (9.0.9) 9.8 seconds
EF10 (10.0.0-rc.1.25451.107) 9 seconds

And as List<string> with no converter:

EF Version Time
EF8 (8.0.20) 5.1 seconds
EF9 (9.0.9) 5.4 seconds
EF10 (10.0.0-rc.1.25451.107) 3.1 seconds

EF Core version

9.0.9, 10.0.0-rc.1.25451.107

Database provider

Microsoft.EntityFrameworkCore.SqlServer

Target framework

.NET9.0, .NET10.0

Operating system

MacOS

IDE

Rider 2025.2.0.1

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions