From ab769deda5ed6a5a5e54ad3cdfebd0d6a43beb84 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 26 Sep 2025 20:06:33 +0000
Subject: [PATCH 1/8] Initial plan
From 76f1be5a41aad3045309f0833560cde23f226b66 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 26 Sep 2025 20:16:47 +0000
Subject: [PATCH 2/8] Add breaking changes documentation for complex types and
compiled models with value converters
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../advanced-performance-topics.md | 49 +++++++
.../ef-core-10.0/breaking-changes.md | 137 ++++++++++++++++++
.../CompiledModels/ValueConverterSample.cs | 115 +++++++++++++++
3 files changed, 301 insertions(+)
create mode 100644 samples/core/Miscellaneous/CompiledModels/ValueConverterSample.cs
diff --git a/entity-framework/core/performance/advanced-performance-topics.md b/entity-framework/core/performance/advanced-performance-topics.md
index bcd662ae1f..829c5bf323 100644
--- a/entity-framework/core/performance/advanced-performance-topics.md
+++ b/entity-framework/core/performance/advanced-performance-topics.md
@@ -295,6 +295,7 @@ Compiled models have some limitations:
* [Global query filters are not supported](https://github.com/dotnet/efcore/issues/24897).
* [Lazy loading and change-tracking proxies are not supported](https://github.com/dotnet/efcore/issues/24902).
+* [Value converters that reference private methods are not supported](#value-converters-private-methods). Make referenced methods public or internal instead.
* [The model must be manually synchronized by regenerating it any time the model definition or configuration change](https://github.com/dotnet/efcore/issues/24894).
* Custom IModelCacheKeyFactory implementations are not supported. However, you can compile multiple models and load the appropriate one as needed.
@@ -302,6 +303,54 @@ Because of these limitations, you should only use compiled models if your EF Cor
If supporting any of these features is critical to your success, then please vote for the appropriate issues linked above.
+
+
+#### Value converters with private methods
+
+When using [value converters](xref:core/modeling/value-conversions) with compiled models, ensure that any methods referenced by the converter are accessible (public or internal, not private). For example, this value converter will cause compilation errors when used with compiled models:
+
+```c#
+public sealed class BooleanToCharConverter : ValueConverter
+{
+ public BooleanToCharConverter()
+ : base(v => ConvertToChar(v), v => ConvertToBoolean(v)) // References private methods
+ {
+ }
+
+ private static char ConvertToChar(bool value) // This will cause compilation errors
+ {
+ return value ? 'Y' : 'N';
+ }
+
+ private static bool ConvertToBoolean(char value) // This will cause compilation errors
+ {
+ return value == 'Y';
+ }
+}
+```
+
+Instead, make the methods public or internal:
+
+```c#
+public sealed class BooleanToCharConverter : ValueConverter
+{
+ public BooleanToCharConverter()
+ : base(v => ConvertToChar(v), v => ConvertToBoolean(v))
+ {
+ }
+
+ public static char ConvertToChar(bool value) // Now accessible
+ {
+ return value ? 'Y' : 'N';
+ }
+
+ public static bool ConvertToBoolean(char value) // Now accessible
+ {
+ return value == 'Y';
+ }
+}
+```
+
## Reducing runtime overhead
As with any layer, EF Core adds a bit of runtime overhead compared to coding directly against lower-level database APIs. This runtime overhead is unlikely to impact most real-world applications in a significant way; the other topics in this performance guide, such as query efficiency, index usage and minimizing roundtrips, are far more important. In addition, even for highly-optimized applications, network latency and database I/O will usually dominate any time spent inside EF Core itself. However, for high-performance, low-latency applications where every bit of perf is important, the following recommendations can be used to reduce EF Core overhead to a minimum:
diff --git a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
index 070c493be1..69466d9a36 100644
--- a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
+++ b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
@@ -24,6 +24,8 @@ This page documents API and behavior changes that have the potential to break ex
|:--------------------------------------------------------------------------------------------------------------- | -----------|
| [SQL Server json data type used by default on Azure SQL and compatibility level 170](#sqlserver-json-data-type) | Low |
| [ExecuteUpdateAsync now accepts a regular, non-expression lambda](#ExecuteUpdateAsync-lambda) | Low |
+| [Compiled models now throw exception for value converters with private methods](#compiled-model-private-methods) | Low |
+| [Complex types are now recommended over owned entity types for JSON and table splitting](#complex-types-recommendation) | Low |
## Low-impact changes
@@ -178,6 +180,141 @@ await context.Blogs.ExecuteUpdateAsync(s =>
});
```
+
+
+### Compiled models now throw exception for value converters with private methods
+
+[Tracking Issue #35033](https://github.com/dotnet/efcore/issues/35033)
+
+#### Old behavior
+
+Previously, when using value converters that referenced private methods with compiled models (using `dotnet ef dbcontext optimize`), EF would generate code that attempted to call these private methods, resulting in compilation errors at build time. For example:
+
+```c#
+public sealed class BooleanToCharConverter : ValueConverter
+{
+ public static readonly BooleanToCharConverter Default = new();
+
+ public BooleanToCharConverter()
+ : base(v => ConvertToChar(v), v => ConvertToBoolean(v))
+ {
+ }
+
+ private static char ConvertToChar(bool value) // Private method
+ {
+ return value ? 'Y' : 'N';
+ }
+
+ private static bool ConvertToBoolean(char value) // Private method
+ {
+ return value == 'Y';
+ }
+}
+```
+
+Running `dotnet ef dbcontext optimize` would generate code that attempted to reference these private methods, causing CS0122 compilation errors.
+
+#### New behavior
+
+Starting with EF Core 10.0, EF will throw an exception during the `dotnet ef dbcontext optimize` command when it detects value converters that reference private methods, preventing the generation of invalid code.
+
+#### Why
+
+This change prevents the generation of code that would fail to compile, providing a clearer error message about the root cause of the issue.
+
+#### Mitigations
+
+Make the methods referenced by value converters public or internal instead of private:
+
+```c#
+public sealed class BooleanToCharConverter : ValueConverter
+{
+ public static readonly BooleanToCharConverter Default = new();
+
+ public BooleanToCharConverter()
+ : base(v => ConvertToChar(v), v => ConvertToBoolean(v))
+ {
+ }
+
+ public static char ConvertToChar(bool value) // Now public
+ {
+ return value ? 'Y' : 'N';
+ }
+
+ public static bool ConvertToBoolean(char value) // Now public
+ {
+ return value == 'Y';
+ }
+}
+```
+
+
+
+### Complex types are now recommended over owned entity types for JSON and table splitting
+
+[Tracking Issue #4970](https://github.com/dotnet/EntityFramework.Docs/issues/4970)
+
+#### Old behavior
+
+Previously, EF Core applications commonly used owned entity types for JSON mapping and table splitting scenarios:
+
+```c#
+modelBuilder.Entity().OwnsOne(c => c.Address, a => a.ToJson());
+```
+
+#### New behavior
+
+Starting with EF Core 10.0, complex types are now the recommended approach for JSON mapping and table splitting, as they provide better semantics and behavior for value-like objects:
+
+```c#
+modelBuilder.Entity().ComplexProperty(c => c.Address, c => c.ToJson());
+```
+
+#### Why
+
+Owned entity types create issues because they still have entity identity semantics behind the scenes, leading to problems such as:
+
+- Cannot assign the same owned entity instance to multiple properties (e.g., `customer.BillingAddress = customer.ShippingAddress`)
+- Identity-based comparisons in LINQ queries don't work as expected for value objects
+- `ExecuteUpdateAsync` operations are not supported with owned entities in JSON
+
+Complex types solve these issues by providing true value semantics without hidden identity, making them more suitable for modeling value objects like addresses, coordinates, etc.
+
+#### Mitigations
+
+Migrate owned entity types used for JSON mapping and table splitting to complex types:
+
+**Before (owned entities):**
+```c#
+modelBuilder.Entity(b =>
+{
+ b.OwnsOne(c => c.ShippingAddress, a => a.ToJson());
+ b.OwnsOne(c => c.BillingAddress, a => a.ToJson());
+});
+```
+
+**After (complex types):**
+```c#
+modelBuilder.Entity(b =>
+{
+ b.ComplexProperty(c => c.ShippingAddress, c => c.ToJson());
+ b.ComplexProperty(c => c.BillingAddress, c => c.ToJson());
+});
+```
+
+For table splitting without JSON:
+```c#
+modelBuilder.Entity(b =>
+{
+ b.ComplexProperty(c => c.ShippingAddress);
+ b.ComplexProperty(c => c.BillingAddress);
+});
+```
+
+Note that complex types have some limitations compared to owned entities:
+- Collections of complex types are not yet supported for table splitting
+- Complex types cannot be navigated to from other entities (no foreign key relationships)
+
## Microsoft.Data.Sqlite breaking changes
diff --git a/samples/core/Miscellaneous/CompiledModels/ValueConverterSample.cs b/samples/core/Miscellaneous/CompiledModels/ValueConverterSample.cs
new file mode 100644
index 0000000000..e53980f9e4
--- /dev/null
+++ b/samples/core/Miscellaneous/CompiledModels/ValueConverterSample.cs
@@ -0,0 +1,115 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using System;
+using System.Threading.Tasks;
+
+namespace CompiledModels;
+
+// This demonstrates the correct way to create value converters
+// that work with compiled models
+public static class ValueConverterSample
+{
+ public static async Task RunSample()
+ {
+ using var context = new SampleContext();
+ await context.Database.EnsureDeletedAsync();
+ await context.Database.EnsureCreatedAsync();
+
+ // Create and save a model with converted values
+ var model = new SampleEntity
+ {
+ IsActive = true,
+ Status = SampleStatus.Active
+ };
+
+ context.SampleEntities.Add(model);
+ await context.SaveChangesAsync();
+
+ // Query the data back
+ var retrieved = await context.SampleEntities.FirstAsync();
+ Console.WriteLine($"IsActive: {retrieved.IsActive}, Status: {retrieved.Status}");
+ }
+}
+
+public class SampleEntity
+{
+ public int Id { get; set; }
+ public bool IsActive { get; set; }
+ public SampleStatus Status { get; set; }
+}
+
+public enum SampleStatus
+{
+ Inactive,
+ Active,
+ Pending
+}
+
+public class SampleContext : DbContext
+{
+ public DbSet SampleEntities { get; set; }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder
+ .UseSqlite("Data Source=sample.db")
+ .LogTo(Console.WriteLine);
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ // Using the correct value converter that works with compiled models
+ modelBuilder.Entity(b =>
+ {
+ b.Property(e => e.IsActive)
+ .HasConversion(BooleanToCharConverter.Instance);
+
+ b.Property(e => e.Status)
+ .HasConversion();
+ });
+ }
+}
+
+// This is the CORRECT way to create a value converter for compiled models
+// All referenced methods must be public or internal, not private
+public sealed class BooleanToCharConverter : ValueConverter
+{
+ public static readonly BooleanToCharConverter Instance = new();
+
+ public BooleanToCharConverter()
+ : base(v => ConvertToChar(v), v => ConvertToBoolean(v))
+ {
+ }
+
+ // PUBLIC methods - this is required for compiled models
+ public static char ConvertToChar(bool value)
+ {
+ return value ? 'Y' : 'N';
+ }
+
+ // PUBLIC methods - this is required for compiled models
+ public static bool ConvertToBoolean(char value)
+ {
+ return value == 'Y';
+ }
+}
+
+// This would be INCORRECT and cause compilation errors with compiled models:
+/*
+public sealed class IncorrectBooleanToCharConverter : ValueConverter
+{
+ public IncorrectBooleanToCharConverter()
+ : base(v => ConvertToChar(v), v => ConvertToBoolean(v))
+ {
+ }
+
+ // PRIVATE methods - this would cause CS0122 compilation errors with compiled models
+ private static char ConvertToChar(bool value)
+ {
+ return value ? 'Y' : 'N';
+ }
+
+ private static bool ConvertToBoolean(char value)
+ {
+ return value == 'Y';
+ }
+}
+*/
\ No newline at end of file
From 829de18ed8540374261e84dd19e7fc1aaa04443d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 26 Sep 2025 22:36:31 +0000
Subject: [PATCH 3/8] Address review feedback: fix breaking changes and remove
unnecessary sections
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../advanced-performance-topics.md | 48 --------
.../ef-core-10.0/breaking-changes.md | 113 ++++++++++-------
.../CompiledModels/ValueConverterSample.cs | 115 ------------------
3 files changed, 72 insertions(+), 204 deletions(-)
delete mode 100644 samples/core/Miscellaneous/CompiledModels/ValueConverterSample.cs
diff --git a/entity-framework/core/performance/advanced-performance-topics.md b/entity-framework/core/performance/advanced-performance-topics.md
index 829c5bf323..130b848d4b 100644
--- a/entity-framework/core/performance/advanced-performance-topics.md
+++ b/entity-framework/core/performance/advanced-performance-topics.md
@@ -303,54 +303,6 @@ Because of these limitations, you should only use compiled models if your EF Cor
If supporting any of these features is critical to your success, then please vote for the appropriate issues linked above.
-
-
-#### Value converters with private methods
-
-When using [value converters](xref:core/modeling/value-conversions) with compiled models, ensure that any methods referenced by the converter are accessible (public or internal, not private). For example, this value converter will cause compilation errors when used with compiled models:
-
-```c#
-public sealed class BooleanToCharConverter : ValueConverter
-{
- public BooleanToCharConverter()
- : base(v => ConvertToChar(v), v => ConvertToBoolean(v)) // References private methods
- {
- }
-
- private static char ConvertToChar(bool value) // This will cause compilation errors
- {
- return value ? 'Y' : 'N';
- }
-
- private static bool ConvertToBoolean(char value) // This will cause compilation errors
- {
- return value == 'Y';
- }
-}
-```
-
-Instead, make the methods public or internal:
-
-```c#
-public sealed class BooleanToCharConverter : ValueConverter
-{
- public BooleanToCharConverter()
- : base(v => ConvertToChar(v), v => ConvertToBoolean(v))
- {
- }
-
- public static char ConvertToChar(bool value) // Now accessible
- {
- return value ? 'Y' : 'N';
- }
-
- public static bool ConvertToBoolean(char value) // Now accessible
- {
- return value == 'Y';
- }
-}
-```
-
## Reducing runtime overhead
As with any layer, EF Core adds a bit of runtime overhead compared to coding directly against lower-level database APIs. This runtime overhead is unlikely to impact most real-world applications in a significant way; the other topics in this performance guide, such as query efficiency, index usage and minimizing roundtrips, are far more important. In addition, even for highly-optimized applications, network latency and database I/O will usually dominate any time spent inside EF Core itself. However, for high-performance, low-latency applications where every bit of perf is important, the following recommendations can be used to reduce EF Core overhead to a minimum:
diff --git a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
index 69466d9a36..0f339def5d 100644
--- a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
+++ b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
@@ -25,7 +25,9 @@ This page documents API and behavior changes that have the potential to break ex
| [SQL Server json data type used by default on Azure SQL and compatibility level 170](#sqlserver-json-data-type) | Low |
| [ExecuteUpdateAsync now accepts a regular, non-expression lambda](#ExecuteUpdateAsync-lambda) | Low |
| [Compiled models now throw exception for value converters with private methods](#compiled-model-private-methods) | Low |
-| [Complex types are now recommended over owned entity types for JSON and table splitting](#complex-types-recommendation) | Low |
+| [Complex type column names are now uniquified](#complex-type-column-uniquification) | Low |
+| [IDiscriminatorPropertySetConvention signature changed](#discriminator-convention-signature) | Low |
+| [Nested complex type properties use full path in column names](#nested-complex-type-column-names) | Low |
## Low-impact changes
@@ -188,7 +190,7 @@ await context.Blogs.ExecuteUpdateAsync(s =>
#### Old behavior
-Previously, when using value converters that referenced private methods with compiled models (using `dotnet ef dbcontext optimize`), EF would generate code that attempted to call these private methods, resulting in compilation errors at build time. For example:
+Previously, when using value converters with compiled models (using `dotnet ef dbcontext optimize`), EF would reference the converter type and everything worked correctly.
```c#
public sealed class BooleanToCharConverter : ValueConverter
@@ -216,11 +218,11 @@ Running `dotnet ef dbcontext optimize` would generate code that attempted to ref
#### New behavior
-Starting with EF Core 10.0, EF will throw an exception during the `dotnet ef dbcontext optimize` command when it detects value converters that reference private methods, preventing the generation of invalid code.
+Starting with EF Core 10.0, EF generates code that directly references the conversion methods themselves. If these methods are private, compilation will fail.
#### Why
-This change prevents the generation of code that would fail to compile, providing a clearer error message about the root cause of the issue.
+This change improves performance by generating more direct code, but requires that conversion methods be accessible to the generated code.
#### Mitigations
@@ -248,73 +250,102 @@ public sealed class BooleanToCharConverter : ValueConverter
}
```
-
+
-### Complex types are now recommended over owned entity types for JSON and table splitting
+### Complex type column names are now uniquified
[Tracking Issue #4970](https://github.com/dotnet/EntityFramework.Docs/issues/4970)
#### Old behavior
-Previously, EF Core applications commonly used owned entity types for JSON mapping and table splitting scenarios:
-
-```c#
-modelBuilder.Entity().OwnsOne(c => c.Address, a => a.ToJson());
-```
+Previously, when mapping complex types to table columns, if multiple complex type properties had the same column name, they would silently share the same column.
#### New behavior
-Starting with EF Core 10.0, complex types are now the recommended approach for JSON mapping and table splitting, as they provide better semantics and behavior for value-like objects:
-
-```c#
-modelBuilder.Entity().ComplexProperty(c => c.Address, c => c.ToJson());
-```
+Starting with EF Core 10.0, complex type column names are uniquified by appending a number at the end if another column with the same name exists on the table.
#### Why
-Owned entity types create issues because they still have entity identity semantics behind the scenes, leading to problems such as:
-
-- Cannot assign the same owned entity instance to multiple properties (e.g., `customer.BillingAddress = customer.ShippingAddress`)
-- Identity-based comparisons in LINQ queries don't work as expected for value objects
-- `ExecuteUpdateAsync` operations are not supported with owned entities in JSON
-
-Complex types solve these issues by providing true value semantics without hidden identity, making them more suitable for modeling value objects like addresses, coordinates, etc.
+This prevents data corruption that could occur when multiple properties unintentionally mapped to the same column.
#### Mitigations
-Migrate owned entity types used for JSON mapping and table splitting to complex types:
+If you need specific column names, configure them explicitly:
-**Before (owned entities):**
```c#
modelBuilder.Entity(b =>
{
- b.OwnsOne(c => c.ShippingAddress, a => a.ToJson());
- b.OwnsOne(c => c.BillingAddress, a => a.ToJson());
+ b.ComplexProperty(c => c.ShippingAddress, p => p.Property(a => a.Street).HasColumnName("ShippingStreet"));
+ b.ComplexProperty(c => c.BillingAddress, p => p.Property(a => a.Street).HasColumnName("BillingStreet"));
});
```
-**After (complex types):**
+
+
+### IDiscriminatorPropertySetConvention signature changed
+
+[Tracking Issue #4970](https://github.com/dotnet/EntityFramework.Docs/issues/4970)
+
+#### Old behavior
+
+Previously, `IDiscriminatorPropertySetConvention.ProcessDiscriminatorPropertySet` took `IConventionEntityTypeBuilder` as a parameter.
+
+#### New behavior
+
+Starting with EF Core 10.0, the method signature changed to take `IConventionTypeBaseBuilder` instead of `IConventionEntityTypeBuilder`.
+
+#### Why
+
+This change allows the convention to work with both entity types and complex types.
+
+#### Mitigations
+
+Update your custom convention implementations to use the new signature:
+
```c#
-modelBuilder.Entity(b =>
-{
- b.ComplexProperty(c => c.ShippingAddress, c => c.ToJson());
- b.ComplexProperty(c => c.BillingAddress, c => c.ToJson());
-});
+public virtual void ProcessDiscriminatorPropertySet(
+ IConventionTypeBaseBuilder typeBaseBuilder, // Changed from IConventionEntityTypeBuilder
+ string name,
+ Type type,
+ MemberInfo memberInfo,
+ IConventionContext context)
```
-For table splitting without JSON:
+
+
+### Nested complex type properties use full path in column names
+
+[Tracking Issue #4947](https://github.com/dotnet/EntityFramework.Docs/issues/4947)
+
+#### Old behavior
+
+Previously, properties on nested complex types were mapped to columns using just the declaring type name. For example, `EntityType.Owned.Complex.Property` was mapped to column `Complex_Property`.
+
+#### New behavior
+
+Starting with EF Core 10.0, properties on nested complex types use the full path to the property as part of the column name. For example, `EntityType.Owned.Complex.Property` is now mapped to column `Owned_Complex_Property`.
+
+#### Why
+
+This provides better column name uniqueness and makes it clearer which property maps to which column.
+
+#### Mitigations
+
+If you need to maintain the old column names, configure them explicitly:
+
```c#
-modelBuilder.Entity(b =>
+modelBuilder.Entity(b =>
{
- b.ComplexProperty(c => c.ShippingAddress);
- b.ComplexProperty(c => c.BillingAddress);
+ b.ComplexProperty(e => e.Owned, owned =>
+ {
+ owned.ComplexProperty(o => o.Complex, complex =>
+ {
+ complex.Property(c => c.Property).HasColumnName("Complex_Property");
+ });
+ });
});
```
-Note that complex types have some limitations compared to owned entities:
-- Collections of complex types are not yet supported for table splitting
-- Complex types cannot be navigated to from other entities (no foreign key relationships)
-
## Microsoft.Data.Sqlite breaking changes
diff --git a/samples/core/Miscellaneous/CompiledModels/ValueConverterSample.cs b/samples/core/Miscellaneous/CompiledModels/ValueConverterSample.cs
deleted file mode 100644
index e53980f9e4..0000000000
--- a/samples/core/Miscellaneous/CompiledModels/ValueConverterSample.cs
+++ /dev/null
@@ -1,115 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using System;
-using System.Threading.Tasks;
-
-namespace CompiledModels;
-
-// This demonstrates the correct way to create value converters
-// that work with compiled models
-public static class ValueConverterSample
-{
- public static async Task RunSample()
- {
- using var context = new SampleContext();
- await context.Database.EnsureDeletedAsync();
- await context.Database.EnsureCreatedAsync();
-
- // Create and save a model with converted values
- var model = new SampleEntity
- {
- IsActive = true,
- Status = SampleStatus.Active
- };
-
- context.SampleEntities.Add(model);
- await context.SaveChangesAsync();
-
- // Query the data back
- var retrieved = await context.SampleEntities.FirstAsync();
- Console.WriteLine($"IsActive: {retrieved.IsActive}, Status: {retrieved.Status}");
- }
-}
-
-public class SampleEntity
-{
- public int Id { get; set; }
- public bool IsActive { get; set; }
- public SampleStatus Status { get; set; }
-}
-
-public enum SampleStatus
-{
- Inactive,
- Active,
- Pending
-}
-
-public class SampleContext : DbContext
-{
- public DbSet SampleEntities { get; set; }
-
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- => optionsBuilder
- .UseSqlite("Data Source=sample.db")
- .LogTo(Console.WriteLine);
-
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- // Using the correct value converter that works with compiled models
- modelBuilder.Entity(b =>
- {
- b.Property(e => e.IsActive)
- .HasConversion(BooleanToCharConverter.Instance);
-
- b.Property(e => e.Status)
- .HasConversion();
- });
- }
-}
-
-// This is the CORRECT way to create a value converter for compiled models
-// All referenced methods must be public or internal, not private
-public sealed class BooleanToCharConverter : ValueConverter
-{
- public static readonly BooleanToCharConverter Instance = new();
-
- public BooleanToCharConverter()
- : base(v => ConvertToChar(v), v => ConvertToBoolean(v))
- {
- }
-
- // PUBLIC methods - this is required for compiled models
- public static char ConvertToChar(bool value)
- {
- return value ? 'Y' : 'N';
- }
-
- // PUBLIC methods - this is required for compiled models
- public static bool ConvertToBoolean(char value)
- {
- return value == 'Y';
- }
-}
-
-// This would be INCORRECT and cause compilation errors with compiled models:
-/*
-public sealed class IncorrectBooleanToCharConverter : ValueConverter
-{
- public IncorrectBooleanToCharConverter()
- : base(v => ConvertToChar(v), v => ConvertToBoolean(v))
- {
- }
-
- // PRIVATE methods - this would cause CS0122 compilation errors with compiled models
- private static char ConvertToChar(bool value)
- {
- return value ? 'Y' : 'N';
- }
-
- private static bool ConvertToBoolean(char value)
- {
- return value == 'Y';
- }
-}
-*/
\ No newline at end of file
From 71791b667018fc12909f34175ae0570f466549d9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 26 Sep 2025 23:18:08 +0000
Subject: [PATCH 4/8] Address additional review feedback: remove links, update
descriptions, reorganize sections
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../advanced-performance-topics.md | 2 +-
.../ef-core-10.0/breaking-changes.md | 95 ++++++-------------
2 files changed, 32 insertions(+), 65 deletions(-)
diff --git a/entity-framework/core/performance/advanced-performance-topics.md b/entity-framework/core/performance/advanced-performance-topics.md
index 130b848d4b..c39b4bfe67 100644
--- a/entity-framework/core/performance/advanced-performance-topics.md
+++ b/entity-framework/core/performance/advanced-performance-topics.md
@@ -295,7 +295,7 @@ Compiled models have some limitations:
* [Global query filters are not supported](https://github.com/dotnet/efcore/issues/24897).
* [Lazy loading and change-tracking proxies are not supported](https://github.com/dotnet/efcore/issues/24902).
-* [Value converters that reference private methods are not supported](#value-converters-private-methods). Make referenced methods public or internal instead.
+* Value converters that reference private methods are not supported. Make referenced methods public or internal instead.
* [The model must be manually synchronized by regenerating it any time the model definition or configuration change](https://github.com/dotnet/efcore/issues/24894).
* Custom IModelCacheKeyFactory implementations are not supported. However, you can compile multiple models and load the appropriate one as needed.
diff --git a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
index 0f339def5d..9137f66903 100644
--- a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
+++ b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
@@ -26,8 +26,8 @@ This page documents API and behavior changes that have the potential to break ex
| [ExecuteUpdateAsync now accepts a regular, non-expression lambda](#ExecuteUpdateAsync-lambda) | Low |
| [Compiled models now throw exception for value converters with private methods](#compiled-model-private-methods) | Low |
| [Complex type column names are now uniquified](#complex-type-column-uniquification) | Low |
-| [IDiscriminatorPropertySetConvention signature changed](#discriminator-convention-signature) | Low |
| [Nested complex type properties use full path in column names](#nested-complex-type-column-names) | Low |
+| [IDiscriminatorPropertySetConvention signature changed](#discriminator-convention-signature) | Low |
## Low-impact changes
@@ -214,41 +214,17 @@ public sealed class BooleanToCharConverter : ValueConverter
}
```
-Running `dotnet ef dbcontext optimize` would generate code that attempted to reference these private methods, causing CS0122 compilation errors.
-
#### New behavior
Starting with EF Core 10.0, EF generates code that directly references the conversion methods themselves. If these methods are private, compilation will fail.
#### Why
-This change improves performance by generating more direct code, but requires that conversion methods be accessible to the generated code.
+This change was necessary to support NativeAOT.
#### Mitigations
-Make the methods referenced by value converters public or internal instead of private:
-
-```c#
-public sealed class BooleanToCharConverter : ValueConverter
-{
- public static readonly BooleanToCharConverter Default = new();
-
- public BooleanToCharConverter()
- : base(v => ConvertToChar(v), v => ConvertToBoolean(v))
- {
- }
-
- public static char ConvertToChar(bool value) // Now public
- {
- return value ? 'Y' : 'N';
- }
-
- public static bool ConvertToBoolean(char value) // Now public
- {
- return value == 'Y';
- }
-}
-```
+Make the methods referenced by value converters public or internal instead of private.
@@ -258,7 +234,7 @@ public sealed class BooleanToCharConverter : ValueConverter
#### Old behavior
-Previously, when mapping complex types to table columns, if multiple complex type properties had the same column name, they would silently share the same column.
+Previously, when mapping complex types to table columns, if multiple properties in different complex types had the same column name, they would silently share the same column.
#### New behavior
@@ -266,84 +242,75 @@ Starting with EF Core 10.0, complex type column names are uniquified by appendin
#### Why
-This prevents data corruption that could occur when multiple properties unintentionally mapped to the same column.
+This prevents data corruption that could occur when multiple properties are unintentionally mapped to the same column.
#### Mitigations
-If you need specific column names, configure them explicitly:
+If you need multiple properties to share the same column, configure them explicitly:
```c#
modelBuilder.Entity(b =>
{
- b.ComplexProperty(c => c.ShippingAddress, p => p.Property(a => a.Street).HasColumnName("ShippingStreet"));
- b.ComplexProperty(c => c.BillingAddress, p => p.Property(a => a.Street).HasColumnName("BillingStreet"));
+ b.ComplexProperty(c => c.ShippingAddress, p => p.Property(a => a.Street).HasColumnName("Street"));
+ b.ComplexProperty(c => c.BillingAddress, p => p.Property(a => a.Street).HasColumnName("Street"));
});
```
-
-
-### IDiscriminatorPropertySetConvention signature changed
+
-[Tracking Issue #4970](https://github.com/dotnet/EntityFramework.Docs/issues/4970)
+### Nested complex type properties use full path in column names
#### Old behavior
-Previously, `IDiscriminatorPropertySetConvention.ProcessDiscriminatorPropertySet` took `IConventionEntityTypeBuilder` as a parameter.
+Previously, properties on nested complex types were mapped to columns using just the declaring type name. For example, `EntityType.Owned.Complex.Property` was mapped to column `Complex_Property`.
#### New behavior
-Starting with EF Core 10.0, the method signature changed to take `IConventionTypeBaseBuilder` instead of `IConventionEntityTypeBuilder`.
+Starting with EF Core 10.0, properties on nested complex types use the full path to the property as part of the column name. For example, `EntityType.Owned.Complex.Property` is now mapped to column `Owned_Complex_Property`.
#### Why
-This change allows the convention to work with both entity types and complex types.
+This provides better column name uniqueness and makes it clearer which property maps to which column.
#### Mitigations
-Update your custom convention implementations to use the new signature:
+If you need to maintain the old column names, configure them explicitly:
```c#
-public virtual void ProcessDiscriminatorPropertySet(
- IConventionTypeBaseBuilder typeBaseBuilder, // Changed from IConventionEntityTypeBuilder
- string name,
- Type type,
- MemberInfo memberInfo,
- IConventionContext context)
+modelBuilder.Entity()
+ .ComplexProperty(e => e.Owned)
+ .ComplexProperty(o => o.Complex)
+ .Property(c => c.Property)
+ .HasColumnName("Complex_Property");
```
-
-
-### Nested complex type properties use full path in column names
+
-[Tracking Issue #4947](https://github.com/dotnet/EntityFramework.Docs/issues/4947)
+### IDiscriminatorPropertySetConvention signature changed
#### Old behavior
-Previously, properties on nested complex types were mapped to columns using just the declaring type name. For example, `EntityType.Owned.Complex.Property` was mapped to column `Complex_Property`.
+Previously, `IDiscriminatorPropertySetConvention.ProcessDiscriminatorPropertySet` took `IConventionEntityTypeBuilder` as a parameter.
#### New behavior
-Starting with EF Core 10.0, properties on nested complex types use the full path to the property as part of the column name. For example, `EntityType.Owned.Complex.Property` is now mapped to column `Owned_Complex_Property`.
+Starting with EF Core 10.0, the method signature changed to take `IConventionTypeBaseBuilder` instead of `IConventionEntityTypeBuilder`.
#### Why
-This provides better column name uniqueness and makes it clearer which property maps to which column.
+This change allows the convention to work with both entity types and complex types.
#### Mitigations
-If you need to maintain the old column names, configure them explicitly:
+Update your custom convention implementations to use the new signature:
```c#
-modelBuilder.Entity(b =>
-{
- b.ComplexProperty(e => e.Owned, owned =>
- {
- owned.ComplexProperty(o => o.Complex, complex =>
- {
- complex.Property(c => c.Property).HasColumnName("Complex_Property");
- });
- });
-});
+public virtual void ProcessDiscriminatorPropertySet(
+ IConventionTypeBaseBuilder typeBaseBuilder, // Changed from IConventionEntityTypeBuilder
+ string name,
+ Type type,
+ MemberInfo memberInfo,
+ IConventionContext context)
```
From e6cca70c801bc946049e9179bfdcca0286b9151a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 26 Sep 2025 23:25:43 +0000
Subject: [PATCH 5/8] Update nested complex type example to use clearer naming
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../core/what-is-new/ef-core-10.0/breaking-changes.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
index 9137f66903..2de6282ba0 100644
--- a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
+++ b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
@@ -262,11 +262,11 @@ modelBuilder.Entity(b =>
#### Old behavior
-Previously, properties on nested complex types were mapped to columns using just the declaring type name. For example, `EntityType.Owned.Complex.Property` was mapped to column `Complex_Property`.
+Previously, properties on nested complex types were mapped to columns using just the declaring type name. For example, `EntityType.Complex.NestedComplex.Property` was mapped to column `NestedComplex_Property`.
#### New behavior
-Starting with EF Core 10.0, properties on nested complex types use the full path to the property as part of the column name. For example, `EntityType.Owned.Complex.Property` is now mapped to column `Owned_Complex_Property`.
+Starting with EF Core 10.0, properties on nested complex types use the full path to the property as part of the column name. For example, `EntityType.Complex.NestedComplex.Property` is now mapped to column `Complex_NestedComplex_Property`.
#### Why
@@ -278,10 +278,10 @@ If you need to maintain the old column names, configure them explicitly:
```c#
modelBuilder.Entity()
- .ComplexProperty(e => e.Owned)
- .ComplexProperty(o => o.Complex)
+ .ComplexProperty(e => e.Complex)
+ .ComplexProperty(o => o.NestedComplex)
.Property(c => c.Property)
- .HasColumnName("Complex_Property");
+ .HasColumnName("NestedComplex_Property");
```
From 94791e42a64a0a44ea932707457d321160f6dd47 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 30 Sep 2025 11:26:34 +0000
Subject: [PATCH 6/8] Fix markdown table indentation alignment
Co-authored-by: roji <1862641+roji@users.noreply.github.com>
---
.../core/what-is-new/ef-core-10.0/breaking-changes.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
index 2de6282ba0..017f8fdcb6 100644
--- a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
+++ b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
@@ -25,9 +25,9 @@ This page documents API and behavior changes that have the potential to break ex
| [SQL Server json data type used by default on Azure SQL and compatibility level 170](#sqlserver-json-data-type) | Low |
| [ExecuteUpdateAsync now accepts a regular, non-expression lambda](#ExecuteUpdateAsync-lambda) | Low |
| [Compiled models now throw exception for value converters with private methods](#compiled-model-private-methods) | Low |
-| [Complex type column names are now uniquified](#complex-type-column-uniquification) | Low |
-| [Nested complex type properties use full path in column names](#nested-complex-type-column-names) | Low |
-| [IDiscriminatorPropertySetConvention signature changed](#discriminator-convention-signature) | Low |
+| [Complex type column names are now uniquified](#complex-type-column-uniquification) | Low |
+| [Nested complex type properties use full path in column names](#nested-complex-type-column-names) | Low |
+| [IDiscriminatorPropertySetConvention signature changed](#discriminator-convention-signature) | Low |
## Low-impact changes
From 1d1a23e3855e494898eb5ff77b9d582b42d266bc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 30 Sep 2025 16:37:53 +0000
Subject: [PATCH 7/8] Modernize code sample with primary constructor and
expression-bodied methods
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../what-is-new/ef-core-10.0/breaking-changes.md | 15 +++------------
1 file changed, 3 insertions(+), 12 deletions(-)
diff --git a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
index 017f8fdcb6..4d8e6a5be6 100644
--- a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
+++ b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
@@ -193,24 +193,15 @@ await context.Blogs.ExecuteUpdateAsync(s =>
Previously, when using value converters with compiled models (using `dotnet ef dbcontext optimize`), EF would reference the converter type and everything worked correctly.
```c#
-public sealed class BooleanToCharConverter : ValueConverter
+public sealed class BooleanToCharConverter() : ValueConverter(v => ConvertToChar(v), v => ConvertToBoolean(v))
{
public static readonly BooleanToCharConverter Default = new();
- public BooleanToCharConverter()
- : base(v => ConvertToChar(v), v => ConvertToBoolean(v))
- {
- }
-
private static char ConvertToChar(bool value) // Private method
- {
- return value ? 'Y' : 'N';
- }
+ => value ? 'Y' : 'N';
private static bool ConvertToBoolean(char value) // Private method
- {
- return value == 'Y';
- }
+ => value == 'Y';
}
```
From c98638ce2dd02c019ecf86f91d765fae5fe4bfc9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 30 Sep 2025 16:55:25 +0000
Subject: [PATCH 8/8] Move value converter breaking change to EF Core 9.0
documentation
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../ef-core-10.0/breaking-changes.md | 36 -------------------
.../ef-core-9.0/breaking-changes.md | 36 +++++++++++++++++++
2 files changed, 36 insertions(+), 36 deletions(-)
diff --git a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
index 4d8e6a5be6..5f3f2292d6 100644
--- a/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
+++ b/entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md
@@ -24,7 +24,6 @@ This page documents API and behavior changes that have the potential to break ex
|:--------------------------------------------------------------------------------------------------------------- | -----------|
| [SQL Server json data type used by default on Azure SQL and compatibility level 170](#sqlserver-json-data-type) | Low |
| [ExecuteUpdateAsync now accepts a regular, non-expression lambda](#ExecuteUpdateAsync-lambda) | Low |
-| [Compiled models now throw exception for value converters with private methods](#compiled-model-private-methods) | Low |
| [Complex type column names are now uniquified](#complex-type-column-uniquification) | Low |
| [Nested complex type properties use full path in column names](#nested-complex-type-column-names) | Low |
| [IDiscriminatorPropertySetConvention signature changed](#discriminator-convention-signature) | Low |
@@ -182,41 +181,6 @@ await context.Blogs.ExecuteUpdateAsync(s =>
});
```
-
-
-### Compiled models now throw exception for value converters with private methods
-
-[Tracking Issue #35033](https://github.com/dotnet/efcore/issues/35033)
-
-#### Old behavior
-
-Previously, when using value converters with compiled models (using `dotnet ef dbcontext optimize`), EF would reference the converter type and everything worked correctly.
-
-```c#
-public sealed class BooleanToCharConverter() : ValueConverter(v => ConvertToChar(v), v => ConvertToBoolean(v))
-{
- public static readonly BooleanToCharConverter Default = new();
-
- private static char ConvertToChar(bool value) // Private method
- => value ? 'Y' : 'N';
-
- private static bool ConvertToBoolean(char value) // Private method
- => value == 'Y';
-}
-```
-
-#### New behavior
-
-Starting with EF Core 10.0, EF generates code that directly references the conversion methods themselves. If these methods are private, compilation will fail.
-
-#### Why
-
-This change was necessary to support NativeAOT.
-
-#### Mitigations
-
-Make the methods referenced by value converters public or internal instead of private.
-
### Complex type column names are now uniquified
diff --git a/entity-framework/core/what-is-new/ef-core-9.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-9.0/breaking-changes.md
index 44f38c5155..459cea6c47 100644
--- a/entity-framework/core/what-is-new/ef-core-9.0/breaking-changes.md
+++ b/entity-framework/core/what-is-new/ef-core-9.0/breaking-changes.md
@@ -29,6 +29,7 @@ EF Core 9 targets .NET 8. This means that existing applications that target .NET
| [Exception is thrown when applying migrations in an explicit transaction](#migrations-transaction) | High |
| [`Microsoft.EntityFrameworkCore.Design` not found when using EF tools](#tools-design) | Medium |
| [`EF.Functions.Unhex()` now returns `byte[]?`](#unhex) | Low |
+| [Compiled models now reference value converter methods directly](#compiled-model-private-methods) | Low |
| [SqlFunctionExpression's nullability arguments' arity validated](#sqlfunctionexpression-nullability) | Low |
| [`ToString()` method now returns empty string for `null` instances](#nullable-tostring) | Low |
| [Shared framework dependencies were updated to 9.0.x](#shared-framework-dependencies) | Low |
@@ -228,6 +229,41 @@ var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)
Otherwise, add runtime checks for null on the return value of Unhex().
+
+
+### Compiled models now reference value converter methods directly
+
+[Tracking Issue #35033](https://github.com/dotnet/efcore/issues/35033)
+
+#### Old behavior
+
+Previously, when using value converters with compiled models (using `dotnet ef dbcontext optimize`), EF would reference the converter type and everything worked correctly.
+
+```c#
+public sealed class BooleanToCharConverter() : ValueConverter(v => ConvertToChar(v), v => ConvertToBoolean(v))
+{
+ public static readonly BooleanToCharConverter Default = new();
+
+ private static char ConvertToChar(bool value) // Private method
+ => value ? 'Y' : 'N';
+
+ private static bool ConvertToBoolean(char value) // Private method
+ => value == 'Y';
+}
+```
+
+#### New behavior
+
+Starting with EF Core 9.0, EF generates code that directly references the conversion methods themselves. If these methods are private, compilation will fail.
+
+#### Why
+
+This change was necessary to support NativeAOT.
+
+#### Mitigations
+
+Make the methods referenced by value converters public or internal instead of private.
+
### SqlFunctionExpression's nullability arguments' arity validated