Skip to content

Commit a29f30c

Browse files
authored
Perf: Cache candidate properties for propagation and generation (#22275)
Resolves #22263 Also improves perf for Add
1 parent 2b6334e commit a29f30c

File tree

5 files changed

+59
-23
lines changed

5 files changed

+59
-23
lines changed

src/EFCore/ChangeTracking/Internal/EntityReferenceMap.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ public virtual void Update(
5050
EntityState state,
5151
EntityState? oldState)
5252
{
53-
var mapKey = entry.Entity ?? entry;
5453
var entityType = entry.EntityType;
5554
if (_hasSubMap
5655
&& entityType.HasDefiningNavigation())
@@ -70,6 +69,8 @@ public virtual void Update(
7069
}
7170
else
7271
{
72+
var mapKey = entry.Entity ?? entry;
73+
7374
if (oldState.HasValue)
7475
{
7576
Remove(mapKey, entityType, oldState.Value);

src/EFCore/ChangeTracking/Internal/IValueGenerationManager.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public interface IValueGenerationManager
3131
/// any release. You should only use it directly in your code with extreme caution and knowing that
3232
/// doing so can result in application failures when updating to a new Entity Framework Core release.
3333
/// </summary>
34-
void Generate([NotNull] InternalEntityEntry entry, bool includePKs = true);
34+
void Generate([NotNull] InternalEntityEntry entry, bool includePrimaryKey = true);
3535

3636
/// <summary>
3737
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -49,7 +49,7 @@ public interface IValueGenerationManager
4949
/// </summary>
5050
Task GenerateAsync(
5151
[NotNull] InternalEntityEntry entry,
52-
bool includePKs = true,
52+
bool includePrimaryKey = true,
5353
CancellationToken cancellationToken = default);
5454

5555
/// <summary>

src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public virtual void SetEntityState(
133133

134134
if (adding || oldState is EntityState.Detached)
135135
{
136-
StateManager.ValueGenerationManager.Generate(this, includePKs: adding);
136+
StateManager.ValueGenerationManager.Generate(this, includePrimaryKey: adding);
137137
}
138138

139139
SetEntityState(oldState, entityState, acceptChanges, modifyProperties);
@@ -159,7 +159,7 @@ public virtual async Task SetEntityStateAsync(
159159

160160
if (adding || oldState is EntityState.Detached)
161161
{
162-
await StateManager.ValueGenerationManager.GenerateAsync(this, includePKs: adding, cancellationToken)
162+
await StateManager.ValueGenerationManager.GenerateAsync(this, includePrimaryKey: adding, cancellationToken)
163163
.ConfigureAwait(false);
164164
}
165165

src/EFCore/ChangeTracking/Internal/StateManager.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,9 @@ private void UpdateReferenceMaps(
324324
EntityState? oldState)
325325
{
326326
var entityType = entry.EntityType;
327-
var mapKey = entry.Entity ?? entry;
328327
if (entityType.HasDefiningNavigation())
329328
{
329+
var mapKey = entry.Entity ?? entry;
330330
foreach (var otherType in _model.GetEntityTypes(entityType.Name)
331331
.Where(et => et != entityType && TryGetEntry(mapKey, et) != null))
332332
{

src/EFCore/ChangeTracking/Internal/ValueGenerationManager.cs

+52-17
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ public class ValueGenerationManager : IValueGenerationManager
3333
private readonly IKeyPropagator _keyPropagator;
3434
private readonly IDiagnosticsLogger<DbLoggerCategory.ChangeTracking> _logger;
3535
private readonly ILoggingOptions _loggingOptions;
36+
private readonly Dictionary<IEntityType, List<IProperty>> _entityTypePropagatingPropertiesMap
37+
= new Dictionary<IEntityType, List<IProperty>>();
38+
private readonly Dictionary<IEntityType, List<IProperty>> _entityTypeGeneratingPropertiesMap
39+
= new Dictionary<IEntityType, List<IProperty>>();
3640

3741
/// <summary>
3842
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -61,8 +65,13 @@ public ValueGenerationManager(
6165
public virtual InternalEntityEntry Propagate(InternalEntityEntry entry)
6266
{
6367
InternalEntityEntry chosenPrincipal = null;
64-
foreach (var property in FindPropagatingProperties(entry))
68+
foreach (var property in FindCandidatePropagatingProperties(entry))
6569
{
70+
if (!entry.HasDefaultValue(property))
71+
{
72+
continue;
73+
}
74+
6675
var principalEntry = _keyPropagator.PropagateValue(entry, property);
6776
if (chosenPrincipal == null)
6877
{
@@ -79,12 +88,19 @@ public virtual InternalEntityEntry Propagate(InternalEntityEntry entry)
7988
/// any release. You should only use it directly in your code with extreme caution and knowing that
8089
/// doing so can result in application failures when updating to a new Entity Framework Core release.
8190
/// </summary>
82-
public virtual void Generate(InternalEntityEntry entry, bool includePKs = true)
91+
public virtual void Generate(InternalEntityEntry entry, bool includePrimaryKey = true)
8392
{
8493
var entityEntry = new EntityEntry(entry);
8594

86-
foreach (var property in FindGeneratingProperties(entry, includePKs))
95+
foreach (var property in FindCandidateGeneratingProperties(entry))
8796
{
97+
if (!entry.HasDefaultValue(property)
98+
|| (!includePrimaryKey
99+
&& property.IsPrimaryKey()))
100+
{
101+
continue;
102+
}
103+
88104
var valueGenerator = GetValueGenerator(entry, property);
89105

90106
var generatedValue = valueGenerator.Next(entityEntry);
@@ -120,13 +136,20 @@ private void Log(InternalEntityEntry entry, IProperty property, object generated
120136
/// </summary>
121137
public virtual async Task GenerateAsync(
122138
InternalEntityEntry entry,
123-
bool includePKs = true,
139+
bool includePrimaryKey = true,
124140
CancellationToken cancellationToken = default)
125141
{
126142
var entityEntry = new EntityEntry(entry);
127143

128-
foreach (var property in FindGeneratingProperties(entry, includePKs))
144+
foreach (var property in FindCandidateGeneratingProperties(entry))
129145
{
146+
if (!entry.HasDefaultValue(property)
147+
|| (!includePrimaryKey
148+
&& property.IsPrimaryKey()))
149+
{
150+
continue;
151+
}
152+
130153
var valueGenerator = GetValueGenerator(entry, property);
131154
var generatedValue = await valueGenerator.NextAsync(entityEntry, cancellationToken)
132155
.ConfigureAwait(false);
@@ -142,30 +165,42 @@ public virtual async Task GenerateAsync(
142165
}
143166
}
144167

145-
private static IEnumerable<IProperty> FindPropagatingProperties(InternalEntityEntry entry)
168+
private List<IProperty> FindCandidatePropagatingProperties(InternalEntityEntry entry)
146169
{
147-
foreach (var property in ((EntityType)entry.EntityType).GetProperties())
170+
if (!_entityTypePropagatingPropertiesMap.TryGetValue(entry.EntityType, out var candidateProperties))
148171
{
149-
if (property.IsForeignKey()
150-
&& entry.HasDefaultValue(property))
172+
candidateProperties = new List<IProperty>();
173+
foreach (var property in entry.EntityType.GetProperties())
151174
{
152-
yield return property;
175+
if (property.IsForeignKey())
176+
{
177+
candidateProperties.Add(property);
178+
}
153179
}
180+
181+
_entityTypePropagatingPropertiesMap[entry.EntityType] = candidateProperties;
154182
}
183+
184+
return candidateProperties;
155185
}
156186

157-
private static IEnumerable<IProperty> FindGeneratingProperties(InternalEntityEntry entry, bool includePKs = true)
187+
private List<IProperty> FindCandidateGeneratingProperties(InternalEntityEntry entry)
158188
{
159-
foreach (var property in ((EntityType)entry.EntityType).GetProperties())
189+
if (!_entityTypeGeneratingPropertiesMap.TryGetValue(entry.EntityType, out var candidateProperties))
160190
{
161-
if (property.RequiresValueGenerator()
162-
&& entry.HasDefaultValue(property)
163-
&& (includePKs
164-
|| !property.IsPrimaryKey()))
191+
candidateProperties = new List<IProperty>();
192+
foreach (var property in entry.EntityType.GetProperties())
165193
{
166-
yield return property;
194+
if (property.RequiresValueGenerator())
195+
{
196+
candidateProperties.Add(property);
197+
}
167198
}
199+
200+
_entityTypeGeneratingPropertiesMap[entry.EntityType] = candidateProperties;
168201
}
202+
203+
return candidateProperties;
169204
}
170205

171206
private ValueGenerator GetValueGenerator(InternalEntityEntry entry, IProperty property)

0 commit comments

Comments
 (0)