EF Core 10 Renamed Columns You Didn't Touch
You upgrade to EF Core 10, run dotnet ef migrations add out of habit, and it generates a migration that renames columns on a model you didn't change at all. This isn't a bug in your project — it's two related breaking changes to how EF Core names complex type columns, both aimed at closing the same class of silent bug.
// change 1 — colliding column names no longer silently share a column
public class Customer { public int Id { get; set; } public Address ShippingAddress { get; set; } public Address BillingAddress { get; set; } } public class Address { public string Street { get; set; } } modelBuilder.Entity<Customer>(b => { 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")); });
Both ComplexProperty calls map to the same "Street" column. No error at migration time, no error at runtime -- customer.ShippingAddress.Street and customer.BillingAddress.Street silently read and write the exact same value.
dotnet ef migrations add PostUpgradeCheck generates a migration renaming one of the columns -- e.g. "Street" stays for ShippingAddress, BillingAddress's Street becomes "Street1" -- so the two properties finally get separate storage instead of quietly sharing one.
// change 2 — nested complex types now use the full property path
A complex type can itself contain another complex type. Before EF Core 10, a nested complex type's columns were named from the nested type alone, dropping the outer property entirely — so Order.ShippingAddress.Coordinates.Lat mapped to a column named just Coordinates_Lat, identical to whatever Order.BillingAddress.Coordinates.Lat would also produce. Same silent-collision risk as change 1, just one level deeper in the object graph.
public class Order { public int Id { get; set; } public Address ShippingAddress { get; set; } public Address BillingAddress { get; set; } } public class Address { public string Street { get; set; } public GeoCoordinates Coordinates { get; set; } // a complex type nested inside a complex type } public class GeoCoordinates { public double Lat { get; set; } public double Lng { get; set; } }
Coordinates_Lat — just the nested type's name, so ShippingAddress.Coordinates.Lat and BillingAddress.Coordinates.Lat resolve to the identical column nameShippingAddress_Coordinates_Lat and BillingAddress_Coordinates_Lat — the full path from the entity down, so the two stay distinct on their own// keeping your existing column names
Both changes only kick in when EF Core is deriving a name on its own. Explicit configuration always wins, on any EF Core version — so if a rename would break existing views, reports, or raw SQL that reference the old column name, pin it down instead of letting the migration go through:
modelBuilder.Entity<Order>()
.ComplexProperty(e => e.ShippingAddress)
.ComplexProperty(o => o.Coordinates)
.Property(c => c.Lat)
.HasColumnName("Coordinates_Lat"); // keeps the pre-EF-Core-10 name on purposeEither way, treat the migration EF Core generates right after the upgrade as a diagnostic, not a formality: it's pointing at every complex-type column name that changed, which is also exactly the set of names that were ambiguous before you upgraded.
// if this is part of a wider .NET 10 migration
This tends to surface in the same sitting as other EF Core migration cleanup — if Update-Database is also reporting nothing to apply when it should be, that's a separate, unrelated mismatch worth ruling out first. And if the database involved is SQLite, .NET 10 also changed how Microsoft.Data.Sqlite interprets stored timestamps — the same class of bug as this one: code that still runs, just resolves differently now.
// frequently asked questions
Why did dotnet ef migrations add rename columns I never touched after upgrading to EF Core 10?
EF Core 10 changed how it names columns for complex types in two ways: it now appends a numeric suffix when two complex-type properties would otherwise map to the same column name, and nested complex types now use the full property path instead of just the nested type's name. Neither requires you to have changed your model — the new migration is EF Core re-deriving names under the new rules.
Is the EF Core 10 complex type column naming change fixing a real bug?
Yes for the collision case. Before EF Core 10, if two different complex-type properties resolved to the same column name, EF Core let them silently share one physical column — writes to one property could overwrite the other. EF Core 10 detects the collision and appends a numeric suffix so each property gets its own column instead.
How do I keep my existing EF Core column names after upgrading to EF Core 10?
Configure the column name explicitly with HasColumnName on each affected property inside ComplexProperty(). Explicit configuration always wins over the automatic naming rules, so this works whether the rename came from the uniquification suffix or the new nested full-path naming.
What is a complex type in EF Core?
A complex type is a class mapped as part of its owning entity rather than as its own table — its properties become columns on the entity's table, prefixed by the complex property's name by default (for example, an Address complex type on a ShippingAddress property produces ShippingAddress_Street). They were introduced as a first-class concept in EF Core 8.
A complex type's properties become columns on the owning entity's table. If two different complex-type properties end up resolved to the same column name — commonly because both were given the same explicit
HasColumnName— earlier EF Core versions let them share that one physical column without complaint. Writing to one property could silently overwrite what the other one had stored, because as far as the database was concerned, they were never two things.