Skip to content

Commit b0faa52

Browse files
authored
Reduce memory usage during relationship cycle detection by keeping track of visited properties whenever there are FK forks (#33211)
Fixes #33176
1 parent 406121f commit b0faa52

File tree

3 files changed

+80
-14
lines changed

3 files changed

+80
-14
lines changed

src/EFCore/Metadata/Internal/Property.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ public class Property : PropertyBase, IMutableProperty, IConventionProperty, IPr
4040
public static readonly bool UseOldBehavior32422 =
4141
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32422", out var enabled32422) && enabled32422;
4242

43+
private static readonly bool UseOldBehavior33176 =
44+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue33176", out var enabled33176) && enabled33176;
45+
4346
/// <summary>
4447
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
4548
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -911,9 +914,10 @@ public virtual (ValueConverter? ValueConverter, Type? ValueConverterType, Type?
911914
bool throwOnValueConverterConflict = true,
912915
bool throwOnProviderClrTypeConflict = true)
913916
{
914-
Queue<(Property CurrentProperty, Property CycleBreakingPropert, int CyclePosition, int MaxCycleLength)>? queue = null;
915-
(Property CurrentProperty, Property CycleBreakingPropert, int CyclePosition, int MaxCycleLength)? currentNode =
917+
Queue<(Property CurrentProperty, Property CycleBreakingProperty, int CyclePosition, int MaxCycleLength)>? queue = null;
918+
(Property CurrentProperty, Property CycleBreakingProperty, int CyclePosition, int MaxCycleLength)? currentNode =
916919
(this, this, 0, 2);
920+
HashSet<Property>? visitedProperties = null;
917921

918922
ValueConverter? valueConverter = null;
919923
Type? valueConverterType = null;
@@ -922,12 +926,17 @@ public virtual (ValueConverter? ValueConverter, Type? ValueConverterType, Type?
922926
{
923927
var (property, cycleBreakingProperty, cyclePosition, maxCycleLength) = currentNode ?? queue!.Dequeue();
924928
currentNode = null;
925-
if (cyclePosition >= ForeignKey.LongestFkChainAllowedLength)
929+
if (cyclePosition >= ForeignKey.LongestFkChainAllowedLength
930+
|| (!UseOldBehavior33176
931+
&& queue is not null
932+
&& queue.Count >= ForeignKey.LongestFkChainAllowedLength))
926933
{
927934
throw new InvalidOperationException(
928935
CoreStrings.RelationshipCycle(DeclaringType.DisplayName(), Name, "ValueConverter"));
929936
}
930937

938+
visitedProperties?.Add(property);
939+
931940
foreach (var foreignKey in property.GetContainingForeignKeys())
932941
{
933942
for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++)
@@ -956,6 +965,13 @@ public virtual (ValueConverter? ValueConverter, Type? ValueConverterType, Type?
956965
{
957966
queue = new();
958967
queue.Enqueue(currentNode.Value);
968+
visitedProperties = new() { property };
969+
}
970+
971+
if (!UseOldBehavior33176
972+
&& visitedProperties?.Contains(principalProperty) == true)
973+
{
974+
break;
959975
}
960976

961977
if (cyclePosition == maxCycleLength - 1)

test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure;
1111

1212
public class SqlServerModelValidatorTest : RelationalModelValidatorTest
1313
{
14+
[ConditionalFact]
15+
public virtual void Passes_on_TPT_with_nested_owned_types()
16+
{
17+
var modelBuilder = base.CreateConventionModelBuilder();
18+
19+
modelBuilder.Entity<BaseEntity>().UseTptMappingStrategy();
20+
modelBuilder.Entity<ChildA>();
21+
modelBuilder.Entity<ChildB>();
22+
modelBuilder.Entity<ChildC>();
23+
modelBuilder.Entity<ChildD>();
24+
25+
Validate(modelBuilder);
26+
}
27+
1428
public override void Detects_duplicate_columns_in_derived_types_with_different_types()
1529
{
1630
var modelBuilder = CreateConventionModelBuilder();

test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,43 @@ protected class Generic<T> : Abstract
118118
{
119119
}
120120

121-
public class SampleEntity
121+
#nullable enable
122+
protected class BaseEntity
123+
{
124+
public int Id { get; set; }
125+
}
126+
127+
protected class ChildA : BaseEntity
128+
{
129+
public OwnedType OwnedType { get; set; } = null!;
130+
}
131+
132+
protected class ChildB : BaseEntity
133+
{
134+
}
135+
136+
protected class ChildC : BaseEntity
137+
{
138+
}
139+
140+
protected class ChildD : BaseEntity
141+
{
142+
}
143+
144+
[Owned]
145+
protected class OwnedType
146+
{
147+
public NestedOwnedType NestedOwnedType { get; set; } = null!;
148+
}
149+
150+
[Owned]
151+
protected class NestedOwnedType
152+
{
153+
}
154+
155+
#nullable restore
156+
157+
protected class SampleEntity
122158
{
123159
public int Id { get; set; }
124160
public int Number { get; set; }
@@ -131,29 +167,29 @@ public class SampleEntity
131167
public ICollection<SampleEntity> OtherSamples { get; set; }
132168
}
133169

134-
public class AnotherSampleEntity
170+
protected class AnotherSampleEntity
135171
{
136172
public int Id { get; set; }
137173
public ReferencedEntity ReferencedEntity { get; set; }
138174
}
139175

140-
public class ReferencedEntity
176+
protected class ReferencedEntity
141177
{
142178
public int Id { get; set; }
143179
public int SampleEntityId { get; set; }
144180
}
145181

146-
public class SampleEntityMinimal
182+
protected class SampleEntityMinimal
147183
{
148184
public int Id { get; set; }
149185
public ReferencedEntityMinimal ReferencedEntity { get; set; }
150186
}
151187

152-
public class ReferencedEntityMinimal
188+
protected class ReferencedEntityMinimal
153189
{
154190
}
155191

156-
public class AnotherSampleEntityMinimal
192+
protected class AnotherSampleEntityMinimal
157193
{
158194
public int Id { get; set; }
159195
public ReferencedEntityMinimal ReferencedEntity { get; set; }
@@ -373,22 +409,22 @@ protected class DependentFour
373409
public PrincipalFour PrincipalFour { get; set; }
374410
}
375411

376-
public class Blog
412+
protected class Blog
377413
{
378414
public int BlogId { get; set; }
379415
public bool IsDeleted { get; set; }
380416
public ICollection<PicturePost> PicturePosts { get; set; }
381417
public List<BlogOwnedEntity> BlogOwnedEntities { get; set; }
382418
}
383419

384-
public class BlogOwnedEntity
420+
protected class BlogOwnedEntity
385421
{
386422
public int BlogOwnedEntityId { get; set; }
387423
public int BlogId { get; set; }
388424
public Blog Blog { get; set; }
389425
}
390426

391-
public class Post
427+
protected class Post
392428
{
393429
public int PostId { get; set; }
394430
public int BlogId { get; set; }
@@ -397,13 +433,13 @@ public class Post
397433
public Blog Blog { get; set; }
398434
}
399435

400-
public class PicturePost : Post
436+
protected class PicturePost : Post
401437
{
402438
public string PictureUrl { get; set; }
403439
public List<Picture> Pictures { get; set; }
404440
}
405441

406-
public class Picture
442+
protected class Picture
407443
{
408444
public int PictureId { get; set; }
409445
public bool IsDeleted { get; set; }

0 commit comments

Comments
 (0)