Skip to content

Commit 3be8511

Browse files
authored
Don't consider key set if property is not set and is FK and PK is not generated (#24577)
Fixes #23226
1 parent 5b2c876 commit 3be8511

File tree

5 files changed

+358
-15
lines changed

5 files changed

+358
-15
lines changed

Diff for: src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1562,7 +1562,7 @@ public virtual (bool IsGenerated, bool IsSet) IsKeySet
15621562

15631563
if ((HasTemporaryValue(keyProperty)
15641564
|| HasDefaultValue(keyProperty))
1565-
&& (keyGenerated || keyProperty.IsForeignKey()))
1565+
&& (keyGenerated || keyProperty.FindGenerationProperty() != null))
15661566
{
15671567
return (true, false);
15681568
}

Diff for: src/EFCore/ChangeTracking/Internal/NavigationFixer.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ public virtual void NavigationReferenceChanged(
254254
_attacher.AttachGraph(
255255
newTargetEntry,
256256
EntityState.Added,
257-
EntityState.Modified,
257+
entry.EntityState == EntityState.Added && !navigation.IsOnDependent ? EntityState.Added : EntityState.Modified,
258258
forceStateWhenUnknownKey: false);
259259
}
260260
}
@@ -380,7 +380,7 @@ public virtual void NavigationCollectionChanged(
380380
_attacher.AttachGraph(
381381
newTargetEntry,
382382
EntityState.Added,
383-
EntityState.Modified,
383+
entry.EntityState == EntityState.Added ? EntityState.Added : EntityState.Modified,
384384
forceStateWhenUnknownKey: false);
385385
}
386386

Diff for: test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs

+335
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,234 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking
2525
{
2626
public class ChangeTrackerTest
2727
{
28+
[ConditionalTheory]
29+
[InlineData(0, 0)]
30+
[InlineData(1, 0)]
31+
[InlineData(0, 1)]
32+
[InlineData(1, 1)]
33+
public void Can_Add_with_identifying_relationships_dependent_first(int principalKeyValue, int dependentKeyValue)
34+
{
35+
using var context = new EarlyLearningCenter();
36+
37+
var added1 = context.Add(new DependentGG { Id = dependentKeyValue, PrincipalGG = new PrincipalGG { Id = principalKeyValue} }).Entity;
38+
Assert.Equal(EntityState.Added, context.Entry(added1).State);
39+
Assert.Equal(EntityState.Added, context.Entry(added1.PrincipalGG).State);
40+
41+
var added2 = context.Add(new DependentNG { Id = dependentKeyValue, PrincipalNG = new PrincipalNG { Id = principalKeyValue} }).Entity;
42+
Assert.Equal(EntityState.Added, context.Entry(added2).State);
43+
Assert.Equal(EntityState.Added, context.Entry(added2.PrincipalNG).State);
44+
45+
var added3 = context.Add(new DependentNN { Id = dependentKeyValue, PrincipalNN = new PrincipalNN { Id = principalKeyValue} }).Entity;
46+
Assert.Equal(EntityState.Added, context.Entry(added3).State);
47+
Assert.Equal(EntityState.Added, context.Entry(added3.PrincipalNN).State);
48+
49+
var added4 = context.Add(new DependentGN { Id = dependentKeyValue, PrincipalGN = new PrincipalGN { Id = principalKeyValue} }).Entity;
50+
Assert.Equal(EntityState.Added, context.Entry(added4).State);
51+
Assert.Equal(EntityState.Added, context.Entry(added4.PrincipalGN).State);
52+
}
53+
54+
[ConditionalTheory]
55+
[InlineData(0, 0)]
56+
[InlineData(1, 0)]
57+
[InlineData(0, 1)]
58+
[InlineData(1, 1)]
59+
public void Can_Add_with_identifying_relationships_principal_first(int principalKeyValue, int dependentKeyValue)
60+
{
61+
using var context = new EarlyLearningCenter();
62+
63+
var added1 = context.Add(new PrincipalGG { Id = principalKeyValue, DependentGG = new DependentGG { Id = dependentKeyValue} }).Entity;
64+
Assert.Equal(EntityState.Added, context.Entry(added1).State);
65+
Assert.Equal(EntityState.Added, context.Entry(added1.DependentGG).State);
66+
67+
var added2 = context.Add(new PrincipalNG { Id = principalKeyValue, DependentNG = new DependentNG { Id = dependentKeyValue} }).Entity;
68+
Assert.Equal(EntityState.Added, context.Entry(added2).State);
69+
Assert.Equal(EntityState.Added, context.Entry(added2.DependentNG).State);
70+
71+
var added3 = context.Add(new PrincipalNN { Id = principalKeyValue, DependentNN = new DependentNN { Id = dependentKeyValue} }).Entity;
72+
Assert.Equal(EntityState.Added, context.Entry(added3).State);
73+
Assert.Equal(EntityState.Added, context.Entry(added3.DependentNN).State);
74+
75+
var added4 = context.Add(new PrincipalGN { Id = principalKeyValue, DependentGN = new DependentGN { Id = dependentKeyValue} }).Entity;
76+
Assert.Equal(EntityState.Added, context.Entry(added4).State);
77+
Assert.Equal(EntityState.Added, context.Entry(added4.DependentGN).State);
78+
}
79+
80+
[ConditionalFact]
81+
public void Can_Attach_with_identifying_relationships_dependent_first()
82+
{
83+
using var context = new EarlyLearningCenter();
84+
85+
var added1 = context.Attach(new DependentGG { PrincipalGG = new PrincipalGG() }).Entity;
86+
Assert.Equal(EntityState.Added, context.Entry(added1).State);
87+
Assert.Equal(EntityState.Added, context.Entry(added1.PrincipalGG).State);
88+
89+
var added2 = context.Attach(new DependentNG { PrincipalNG = new PrincipalNG() }).Entity;
90+
Assert.Equal(EntityState.Added, context.Entry(added2).State);
91+
Assert.Equal(EntityState.Unchanged, context.Entry(added2.PrincipalNG).State);
92+
93+
var added3 = context.Attach(new DependentNN { PrincipalNN = new PrincipalNN() }).Entity;
94+
Assert.Equal(EntityState.Unchanged, context.Entry(added3).State);
95+
Assert.Equal(EntityState.Unchanged, context.Entry(added3.PrincipalNN).State);
96+
97+
var added4 = context.Attach(new DependentGN { PrincipalGN = new PrincipalGN() }).Entity;
98+
Assert.Equal(EntityState.Added, context.Entry(added4).State);
99+
Assert.Equal(EntityState.Added, context.Entry(added4.PrincipalGN).State);
100+
}
101+
102+
[ConditionalFact]
103+
public void Can_Attach_with_identifying_relationships_dependent_first_with_principal_keys_set()
104+
{
105+
using var context = new EarlyLearningCenter();
106+
107+
var added1 = context.Attach(new DependentGG { PrincipalGG = new PrincipalGG { Id = 1 } }).Entity;
108+
Assert.Equal(EntityState.Added, context.Entry(added1).State);
109+
Assert.Equal(EntityState.Unchanged, context.Entry(added1.PrincipalGG).State);
110+
111+
var added2 = context.Attach(new DependentNG { PrincipalNG = new PrincipalNG { Id = 1 } }).Entity;
112+
Assert.Equal(EntityState.Added, context.Entry(added2).State);
113+
Assert.Equal(EntityState.Unchanged, context.Entry(added2.PrincipalNG).State);
114+
115+
var added3 = context.Attach(new DependentNN { PrincipalNN = new PrincipalNN { Id = 1 } }).Entity;
116+
Assert.Equal(EntityState.Unchanged, context.Entry(added3).State);
117+
Assert.Equal(EntityState.Unchanged, context.Entry(added3.PrincipalNN).State);
118+
119+
var added4 = context.Attach(new DependentGN { PrincipalGN = new PrincipalGN { Id = 1 } }).Entity;
120+
Assert.Equal(EntityState.Added, context.Entry(added4).State);
121+
Assert.Equal(EntityState.Unchanged, context.Entry(added4.PrincipalGN).State);
122+
}
123+
124+
[ConditionalFact]
125+
public void Can_Attach_with_identifying_relationships_dependent_first_with_dependent_keys_set()
126+
{
127+
using var context = new EarlyLearningCenter();
128+
129+
var added1 = context.Attach(new DependentGG { Id = 1, PrincipalGG = new PrincipalGG() }).Entity;
130+
Assert.Equal(EntityState.Unchanged, context.Entry(added1).State);
131+
Assert.Equal(EntityState.Added, context.Entry(added1.PrincipalGG).State);
132+
133+
var added2 = context.Attach(new DependentNG { Id = 1, PrincipalNG = new PrincipalNG() }).Entity;
134+
Assert.Equal(EntityState.Unchanged, context.Entry(added2).State);
135+
Assert.Equal(EntityState.Unchanged, context.Entry(added2.PrincipalNG).State);
136+
137+
var added3 = context.Attach(new DependentNN { Id = 1, PrincipalNN = new PrincipalNN() }).Entity;
138+
Assert.Equal(EntityState.Unchanged, context.Entry(added3).State);
139+
Assert.Equal(EntityState.Unchanged, context.Entry(added3.PrincipalNN).State);
140+
141+
var added4 = context.Attach(new DependentGN { Id = 1, PrincipalGN = new PrincipalGN() }).Entity;
142+
Assert.Equal(EntityState.Unchanged, context.Entry(added4).State);
143+
Assert.Equal(EntityState.Added, context.Entry(added4.PrincipalGN).State);
144+
}
145+
146+
[ConditionalFact]
147+
public void Can_Attach_with_identifying_relationships_dependent_first_with_all_keys_set()
148+
{
149+
using var context = new EarlyLearningCenter();
150+
151+
var added1 = context.Attach(new DependentGG { Id = 1, PrincipalGG = new PrincipalGG { Id = 1 } }).Entity;
152+
Assert.Equal(EntityState.Unchanged, context.Entry(added1).State);
153+
Assert.Equal(EntityState.Unchanged, context.Entry(added1.PrincipalGG).State);
154+
155+
var added2 = context.Attach(new DependentNG { Id = 1, PrincipalNG = new PrincipalNG { Id = 1 } }).Entity;
156+
Assert.Equal(EntityState.Unchanged, context.Entry(added2).State);
157+
Assert.Equal(EntityState.Unchanged, context.Entry(added2.PrincipalNG).State);
158+
159+
var added3 = context.Attach(new DependentNN { Id = 1, PrincipalNN = new PrincipalNN { Id = 1 } }).Entity;
160+
Assert.Equal(EntityState.Unchanged, context.Entry(added3).State);
161+
Assert.Equal(EntityState.Unchanged, context.Entry(added3.PrincipalNN).State);
162+
163+
var added4 = context.Attach(new DependentGN { Id = 1, PrincipalGN = new PrincipalGN { Id = 1 } }).Entity;
164+
Assert.Equal(EntityState.Unchanged, context.Entry(added4).State);
165+
Assert.Equal(EntityState.Unchanged, context.Entry(added4.PrincipalGN).State);
166+
}
167+
168+
[ConditionalFact]
169+
public void Can_Attach_with_identifying_relationships_principal_first()
170+
{
171+
using var context = new EarlyLearningCenter();
172+
173+
var added1 = context.Attach(new PrincipalGG { DependentGG = new DependentGG() }).Entity;
174+
Assert.Equal(EntityState.Added, context.Entry(added1).State);
175+
Assert.Equal(EntityState.Added, context.Entry(added1.DependentGG).State);
176+
177+
var added2 = context.Attach(new PrincipalNG { DependentNG = new DependentNG() }).Entity;
178+
Assert.Equal(EntityState.Unchanged, context.Entry(added2).State);
179+
Assert.Equal(EntityState.Added, context.Entry(added2.DependentNG).State);
180+
181+
var added3 = context.Attach(new PrincipalNN { DependentNN = new DependentNN() }).Entity;
182+
Assert.Equal(EntityState.Unchanged, context.Entry(added3).State);
183+
Assert.Equal(EntityState.Unchanged, context.Entry(added3.DependentNN).State);
184+
185+
var added4 = context.Attach(new PrincipalGN { DependentGN = new DependentGN() }).Entity;
186+
Assert.Equal(EntityState.Added, context.Entry(added4).State);
187+
Assert.Equal(EntityState.Added, context.Entry(added4.DependentGN).State);
188+
}
189+
190+
[ConditionalFact]
191+
public void Can_Attach_with_identifying_relationships_principal_first_with_principal_keys_set()
192+
{
193+
using var context = new EarlyLearningCenter();
194+
195+
var added1 = context.Attach(new PrincipalGG { Id = 1, DependentGG = new DependentGG() }).Entity;
196+
Assert.Equal(EntityState.Unchanged, context.Entry(added1).State);
197+
Assert.Equal(EntityState.Added, context.Entry(added1.DependentGG).State);
198+
199+
var added2 = context.Attach(new PrincipalNG { Id = 1, DependentNG = new DependentNG() }).Entity;
200+
Assert.Equal(EntityState.Unchanged, context.Entry(added2).State);
201+
Assert.Equal(EntityState.Added, context.Entry(added2.DependentNG).State);
202+
203+
var added3 = context.Attach(new PrincipalNN { Id = 1, DependentNN = new DependentNN() }).Entity;
204+
Assert.Equal(EntityState.Unchanged, context.Entry(added3).State);
205+
Assert.Equal(EntityState.Unchanged, context.Entry(added3.DependentNN).State);
206+
207+
var added4 = context.Attach(new PrincipalGN { Id = 1, DependentGN = new DependentGN() }).Entity;
208+
Assert.Equal(EntityState.Unchanged, context.Entry(added4).State);
209+
Assert.Equal(EntityState.Added, context.Entry(added4.DependentGN).State);
210+
}
211+
212+
[ConditionalFact]
213+
public void Can_Attach_with_identifying_relationships_principal_first_with_dependent_keys_set()
214+
{
215+
using var context = new EarlyLearningCenter();
216+
217+
var added1 = context.Attach(new PrincipalGG { DependentGG = new DependentGG { Id = 1 } }).Entity;
218+
Assert.Equal(EntityState.Added, context.Entry(added1).State);
219+
Assert.Equal(EntityState.Unchanged, context.Entry(added1.DependentGG).State);
220+
221+
var added2 = context.Attach(new PrincipalNG { DependentNG = new DependentNG { Id = 1 } }).Entity;
222+
Assert.Equal(EntityState.Unchanged, context.Entry(added2).State);
223+
Assert.Equal(EntityState.Unchanged, context.Entry(added2.DependentNG).State);
224+
225+
var added3 = context.Attach(new PrincipalNN { DependentNN = new DependentNN { Id = 1 } }).Entity;
226+
Assert.Equal(EntityState.Unchanged, context.Entry(added3).State);
227+
Assert.Equal(EntityState.Unchanged, context.Entry(added3.DependentNN).State);
228+
229+
var added4 = context.Attach(new PrincipalGN { DependentGN = new DependentGN { Id = 1 } }).Entity;
230+
Assert.Equal(EntityState.Added, context.Entry(added4).State);
231+
Assert.Equal(EntityState.Unchanged, context.Entry(added4.DependentGN).State);
232+
}
233+
234+
[ConditionalFact]
235+
public void Can_Attach_with_identifying_relationships_principal_first_with_all_keys_set()
236+
{
237+
using var context = new EarlyLearningCenter();
238+
239+
var added1 = context.Attach(new PrincipalGG { Id = 1, DependentGG = new DependentGG { Id = 1 } }).Entity;
240+
Assert.Equal(EntityState.Unchanged, context.Entry(added1).State);
241+
Assert.Equal(EntityState.Unchanged, context.Entry(added1.DependentGG).State);
242+
243+
var added2 = context.Attach(new PrincipalNG { Id = 1, DependentNG = new DependentNG { Id = 1 } }).Entity;
244+
Assert.Equal(EntityState.Unchanged, context.Entry(added2).State);
245+
Assert.Equal(EntityState.Unchanged, context.Entry(added2.DependentNG).State);
246+
247+
var added3 = context.Attach(new PrincipalNN { Id = 1, DependentNN = new DependentNN { Id = 1 } }).Entity;
248+
Assert.Equal(EntityState.Unchanged, context.Entry(added3).State);
249+
Assert.Equal(EntityState.Unchanged, context.Entry(added3.DependentNN).State);
250+
251+
var added4 = context.Attach(new PrincipalGN { Id = 1, DependentGN = new DependentGN { Id = 1 } }).Entity;
252+
Assert.Equal(EntityState.Unchanged, context.Entry(added4).State);
253+
Assert.Equal(EntityState.Unchanged, context.Entry(added4.DependentGN).State);
254+
}
255+
28256
[ConditionalFact]
29257
public void Change_tracker_can_be_cleared()
30258
{
@@ -2529,6 +2757,54 @@ private class WhoAmI
25292757
public string ToDisagree { get; set; }
25302758
}
25312759

2760+
private class PrincipalGG
2761+
{
2762+
public int Id { get; set; }
2763+
public DependentGG DependentGG { get; set; }
2764+
}
2765+
2766+
private class DependentGG
2767+
{
2768+
public int Id { get; set; }
2769+
public PrincipalGG PrincipalGG { get; set; }
2770+
}
2771+
2772+
private class PrincipalNN
2773+
{
2774+
public int Id { get; set; }
2775+
public DependentNN DependentNN { get; set; }
2776+
}
2777+
2778+
private class DependentNN
2779+
{
2780+
public int Id { get; set; }
2781+
public PrincipalNN PrincipalNN { get; set; }
2782+
}
2783+
2784+
private class PrincipalNG
2785+
{
2786+
public int Id { get; set; }
2787+
public DependentNG DependentNG { get; set; }
2788+
}
2789+
2790+
private class DependentNG
2791+
{
2792+
public int Id { get; set; }
2793+
public PrincipalNG PrincipalNG { get; set; }
2794+
}
2795+
2796+
private class PrincipalGN
2797+
{
2798+
public int Id { get; set; }
2799+
public DependentGN DependentGN { get; set; }
2800+
}
2801+
2802+
private class DependentGN
2803+
{
2804+
public int Id { get; set; }
2805+
public PrincipalGN PrincipalGN { get; set; }
2806+
}
2807+
25322808
private class EarlyLearningCenter : DbContext
25332809
{
25342810
private readonly IServiceProvider _serviceProvider;
@@ -2576,6 +2852,65 @@ protected internal override void OnModelCreating(ModelBuilder modelBuilder)
25762852
});
25772853

25782854
modelBuilder.Entity<OptionalProduct>();
2855+
2856+
modelBuilder.Entity<PrincipalNN>(
2857+
b =>
2858+
{
2859+
b.HasOne(e => e.DependentNN)
2860+
.WithOne(e => e.PrincipalNN)
2861+
.HasForeignKey<DependentNN>(e => e.Id);
2862+
2863+
b.Property(e => e.Id).ValueGeneratedNever();
2864+
});
2865+
2866+
modelBuilder.Entity<DependentNN>().Property(e => e.Id).ValueGeneratedNever();
2867+
2868+
modelBuilder.Entity<PrincipalGG>(
2869+
b =>
2870+
{
2871+
b.HasOne(e => e.DependentGG)
2872+
.WithOne(e => e.PrincipalGG)
2873+
.HasForeignKey<DependentGG>(e => e.Id);
2874+
2875+
b.Property(e => e.Id).ValueGeneratedOnAdd();
2876+
});
2877+
2878+
modelBuilder.Entity<DependentGG>().Property(e => e.Id).ValueGeneratedOnAdd();
2879+
2880+
modelBuilder.Entity<PrincipalNG>(
2881+
b =>
2882+
{
2883+
b.HasOne(e => e.DependentNG)
2884+
.WithOne(e => e.PrincipalNG)
2885+
.HasForeignKey<DependentNG>(e => e.Id);
2886+
2887+
b.Property(e => e.Id).ValueGeneratedNever();
2888+
});
2889+
2890+
modelBuilder.Entity<DependentNG>().Property(e => e.Id).HasValueGenerator<DummyValueGenerator>();
2891+
2892+
modelBuilder.Entity<PrincipalGN>(
2893+
b =>
2894+
{
2895+
b.HasOne(e => e.DependentGN)
2896+
.WithOne(e => e.PrincipalGN)
2897+
.HasForeignKey<DependentGN>(e => e.Id);
2898+
2899+
b.Property(e => e.Id).ValueGeneratedOnAdd();
2900+
});
2901+
2902+
modelBuilder.Entity<DependentGN>().Property(e => e.Id).ValueGeneratedNever();
2903+
}
2904+
2905+
private class DummyValueGenerator : ValueGenerator<int>
2906+
{
2907+
private static int _value;
2908+
2909+
public override int Next(EntityEntry entry)
2910+
=> _value++;
2911+
2912+
public override bool GeneratesTemporaryValues
2913+
=> false;
25792914
}
25802915

25812916
protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

0 commit comments

Comments
 (0)