EF Core 11 breaking changes: assigning the same owned instance to more than one navigation now throws
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 1/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 74/100
- Tipo di issue
- Documentazione
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- csharp
- Ambito
- documentation
Direzione di ricerca
Individua la pagina delle modifiche che introducono breaking change di EF Core 11 e leggi il formato delle voci esistenti. Aggiungi una voce a basso impatto che descriva il comportamento modificato e la mitigazione indicata nell’issue. Poi controlla i link e la formattazione della pagina. Il lavoro è terminato quando la voce spiega chiaramente l’eccezione in fase di esecuzione e consiglia istanze separate o tipi complessi.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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
- Lingua principale
- Mermaid
- Stelle
- 1.7k
- Fork
- 2k
- Merge medio
- 1g 3h
- PR unite (30g)
- 33
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di dotnet/EntityFramework.Docs
-
Difficoltà 2/5 Mezza giornata Idoneità per principianti 72/100
dotnet/EntityFramework.Docs#4774 ·
I maintainer di solito rispondono entro 1 giorno
-
area-change-tracking area-model-building
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 65/100
dotnet/EntityFramework.Docs#4278 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 65/100
dotnet/EntityFramework.Docs#4100 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
area-tools
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 62/100
dotnet/EntityFramework.Docs#3927 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
area-change-tracking
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 62/100
dotnet/EntityFramework.Docs#3792 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di dotnet/EntityFramework.Docs
Issue simili
-
documentation good first issue
Difficoltà 1/5 1-3 ore Idoneità per principianti 78/100
andy1924/notch-player#10 ·
-
README の通知条件を実際の判定処理に合わせるForse già presa @unhappychoice l’ha presa oggi. Apertafactory:in-progress priority:medium
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 90/100
unhappychoice/oss-issue-opener#395 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 90/100
Mintplex-Labs/anything-llm#7133 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
mohi-devhub/antivibe#12 ·
-
documentation good first issue
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
OWASP/OWASP-Hunting#77 ·
I maintainer di solito rispondono entro 1 giorno