EF Core 11 breaking changes: assigning the same owned instance to more than one navigation now throws
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 1/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 74/100
- Issue type
- Documentation
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- csharp
- Domain
- documentation
Research direction
Find the EF Core 11 breaking changes page and read its existing entry format. Add a low impact entry describing the changed behavior and mitigation from the issue, then check the page’s links and formatting. Done when the entry clearly explains the runtime exception and recommends separate instances or complex types.
Written by the indexing model from the issue text.
Description
Issue or suggestion
dotnet/efcore#38446 (11.0 preview 6) made EF throw when the same owned instance is referenced from more than one ownership navigation. The change isn't listed on the 11.0 breaking changes page, and it caught us during the upgrade because one of these patterns saved fine on 10.0:
var customer = db.Customers.Single(c => c.Id == 1);
customer.Billing = customer.Shipping;
db.SaveChanges();
10.0.15 saves it: both column sets get the Shipping values. 11.0.0 throws:
InvalidOperationException: The same object instance of type 'Address' is being referenced from the navigation 'Shipping' on 'Customer' and from the navigation 'Billing' on 'Customer'. Owned entity types cannot have multiple owners pointing to the same instance. Consider creating a copy of the object to use for each navigation.
The PR description says the old behavior was a confusing FK error at SaveChanges. That's what happens in some cases, but not in this one. There's also a second case that didn't throw on 10.0: when two new owners share an optional owned instance, 10.0 saves both, and the first owner silently ends up with null for that navigation (repro below). So the new check is a good change. It's just that code that ran on 10 now fails at runtime after the upgrade, and nothing at build time points to it.
Could this get a low impact entry on the breaking changes page? Roughly:
- Old behavior: sharing an owned instance either failed at SaveChanges with an unrelated error, or saved. When it saved, two navigations of a tracked owner both got the same values, or one of two new owners lost the value.
- New behavior:
InvalidOperationExceptionwhen the entity is tracked or on DetectChanges. - Mitigation: give each navigation its own instance (copy it), or map the type as a complex type, which has value semantics.
I can open a PR for it if that helps.
Repro (SQLite):
#:package [email protected]
#:property PublishAot=false
using Microsoft.EntityFrameworkCore;
using (var db = new AppDb())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
db.Add(new Customer { Id = 1, Shipping = new Address { City = "Paris" }, Billing = new Address { City = "Lyon" } });
db.SaveChanges();
}
Run("Existing customer: Billing = Shipping", db =>
{
var customer = db.Customers.Single(c => c.Id == 1);
customer.Billing = customer.Shipping;
db.SaveChanges();
});
Run("Two new customers share one optional Billing instance", db =>
{
var billing = new Address { City = "Berlin" };
db.Add(new Customer { Id = 2, Shipping = new Address { City = "Rome" }, Billing = billing });
db.Add(new Customer { Id = 3, Shipping = new Address { City = "Oslo" }, Billing = billing });
db.SaveChanges();
});
using (var db = new AppDb())
{
foreach (var c in db.Customers.AsNoTracking().OrderBy(c => c.Id))
{
Console.WriteLine($"Customer {c.Id}: Shipping={c.Shipping.City}, Billing={c.Billing?.City ?? "<null>"}");
}
}
static void Run(string name, Action<AppDb> action)
{
Console.WriteLine(name);
try
{
using var db = new AppDb();
action(db);
Console.WriteLine(" saved");
}
catch (Exception e)
{
Console.WriteLine($" {e.GetType().Name}: {e.Message}");
}
}
public class Customer
{
public int Id { get; set; }
public Address Shipping { get; set; } = null!;
public Address? Billing { get; set; }
}
public class Address
{
public string City { get; set; } = "";
}
public class AppDb : DbContext
{
public DbSet<Customer> Customers => Set<Customer>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite("Data Source=repro.db");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>(b =>
{
b.Property(c => c.Id).ValueGeneratedNever();
b.OwnsOne(c => c.Shipping);
b.OwnsOne(c => c.Billing);
});
}
}
Output with 11.0.0:
Existing customer: Billing = Shipping
InvalidOperationException: The same object instance of type 'Address' is being referenced from the navigation 'Shipping' on 'Customer' and from the navigation 'Billing' on 'Customer'. Owned entity types cannot have multiple owners pointing to the same instance. Consider creating a copy of the object to use for each navigation.
Two new customers share one optional Billing instance
InvalidOperationException: The same object instance of type 'Address' is being referenced from the navigation 'Billing' on 'Customer' and from the navigation 'Billing' on 'Customer'. Owned entity types cannot have multiple owners pointing to the same instance. Consider creating a copy of the object to use for each navigation.
Customer 1: Shipping=Paris, Billing=Lyon
Output with 10.0.15:
Existing customer: Billing = Shipping
saved
Two new customers share one optional Billing instance
saved
Customer 1: Shipping=Paris, Billing=Paris
Customer 2: Shipping=Rome, Billing=<null>
Customer 3: Shipping=Oslo, Billing=Berlin
- Dominant language
- Mermaid
- Stars
- 1.7k
- Forks
- 2k
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 33
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/EntityFramework.Docs
-
Difficulty 2/5 Half a day Newbie friendliness 72/100
dotnet/EntityFramework.Docs#4774 ·
Maintainers usually reply within 1 day
-
area-change-tracking area-model-building
Difficulty 1/5 Under an hour Newbie friendliness 65/100
dotnet/EntityFramework.Docs#4278 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 65/100
dotnet/EntityFramework.Docs#4100 · 1 comment ·
Maintainers usually reply within 1 day
-
area-tools
Difficulty 1/5 Under an hour Newbie friendliness 62/100
dotnet/EntityFramework.Docs#3927 · 1 comment ·
Maintainers usually reply within 1 day
-
area-change-tracking
Difficulty 1/5 Under an hour Newbie friendliness 62/100
dotnet/EntityFramework.Docs#3792 ·
Maintainers usually reply within 1 day
All issues in dotnet/EntityFramework.Docs
Similar issues
-
documentation
Difficulty 1/5 1-3 hours Newbie friendliness 82/100
melihbirim/csvql#311 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 1-3 hours Newbie friendliness 88/100
aws-samples/sample-per-model-bedrock#857 · 1 comment ·
Maintainers usually reply within 1 day
-
documentation maintainer-managed
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
awslabs/visual-asset-management-system#1418 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
documentation good first issue
Difficulty 1/5 1-3 hours Newbie friendliness 78/100
andy1924/notch-player#10 ·