Skip to content

Commit

Permalink
Perf: Cache candidate properties for propagation and generation
Browse files Browse the repository at this point in the history
Resolves #22263

Also improves perf for Add
  • Loading branch information
smitpatel committed Aug 27, 2020
1 parent 7eda77a commit 97e2366
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 23 deletions.
3 changes: 2 additions & 1 deletion src/EFCore/ChangeTracking/Internal/EntityReferenceMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ public virtual void Update(
EntityState state,
EntityState? oldState)
{
var mapKey = entry.Entity ?? entry;
var entityType = entry.EntityType;
if (_hasSubMap
&& entityType.HasDefiningNavigation())
Expand All @@ -70,6 +69,8 @@ public virtual void Update(
}
else
{
var mapKey = entry.Entity ?? entry;

if (oldState.HasValue)
{
Remove(mapKey, entityType, oldState.Value);
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/ChangeTracking/Internal/IValueGenerationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public interface IValueGenerationManager
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
void Generate([NotNull] InternalEntityEntry entry, bool includePKs = true);
void Generate([NotNull] InternalEntityEntry entry, bool includePrimaryKey = true);

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

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public virtual void SetEntityState(

if (adding || oldState is EntityState.Detached)
{
StateManager.ValueGenerationManager.Generate(this, includePKs: adding);
StateManager.ValueGenerationManager.Generate(this, includePrimaryKey: adding);
}

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

if (adding || oldState is EntityState.Detached)
{
await StateManager.ValueGenerationManager.GenerateAsync(this, includePKs: adding, cancellationToken)
await StateManager.ValueGenerationManager.GenerateAsync(this, includePrimaryKey: adding, cancellationToken)
.ConfigureAwait(false);
}

Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/ChangeTracking/Internal/StateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,9 @@ private void UpdateReferenceMaps(
EntityState? oldState)
{
var entityType = entry.EntityType;
var mapKey = entry.Entity ?? entry;
if (entityType.HasDefiningNavigation())
{
var mapKey = entry.Entity ?? entry;
foreach (var otherType in _model.GetEntityTypes(entityType.Name)
.Where(et => et != entityType && TryGetEntry(mapKey, et) != null))
{
Expand Down
69 changes: 52 additions & 17 deletions src/EFCore/ChangeTracking/Internal/ValueGenerationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public class ValueGenerationManager : IValueGenerationManager
private readonly IKeyPropagator _keyPropagator;
private readonly IDiagnosticsLogger<DbLoggerCategory.ChangeTracking> _logger;
private readonly ILoggingOptions _loggingOptions;
private readonly Dictionary<IEntityType, List<IProperty>> _entityTypePropagatingPropertiesMap
= new Dictionary<IEntityType, List<IProperty>>();
private readonly Dictionary<IEntityType, List<IProperty>> _entityTypeGeneratingPropertiesMap
= new Dictionary<IEntityType, List<IProperty>>();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -61,8 +65,13 @@ public ValueGenerationManager(
public virtual InternalEntityEntry Propagate(InternalEntityEntry entry)
{
InternalEntityEntry chosenPrincipal = null;
foreach (var property in FindPropagatingProperties(entry))
foreach (var property in FindCandidatePropagatingProperties(entry))
{
if (!entry.HasDefaultValue(property))
{
continue;
}

var principalEntry = _keyPropagator.PropagateValue(entry, property);
if (chosenPrincipal == null)
{
Expand All @@ -79,12 +88,19 @@ public virtual InternalEntityEntry Propagate(InternalEntityEntry entry)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void Generate(InternalEntityEntry entry, bool includePKs = true)
public virtual void Generate(InternalEntityEntry entry, bool includePrimaryKey = true)
{
var entityEntry = new EntityEntry(entry);

foreach (var property in FindGeneratingProperties(entry, includePKs))
foreach (var property in FindCandidateGeneratingProperties(entry))
{
if (!entry.HasDefaultValue(property)
|| (!includePrimaryKey
&& property.IsPrimaryKey()))
{
continue;
}

var valueGenerator = GetValueGenerator(entry, property);

var generatedValue = valueGenerator.Next(entityEntry);
Expand Down Expand Up @@ -120,13 +136,20 @@ private void Log(InternalEntityEntry entry, IProperty property, object generated
/// </summary>
public virtual async Task GenerateAsync(
InternalEntityEntry entry,
bool includePKs = true,
bool includePrimaryKey = true,
CancellationToken cancellationToken = default)
{
var entityEntry = new EntityEntry(entry);

foreach (var property in FindGeneratingProperties(entry, includePKs))
foreach (var property in FindCandidateGeneratingProperties(entry))
{
if (!entry.HasDefaultValue(property)
|| (!includePrimaryKey
&& property.IsPrimaryKey()))
{
continue;
}

var valueGenerator = GetValueGenerator(entry, property);
var generatedValue = await valueGenerator.NextAsync(entityEntry, cancellationToken)
.ConfigureAwait(false);
Expand All @@ -142,30 +165,42 @@ public virtual async Task GenerateAsync(
}
}

private static IEnumerable<IProperty> FindPropagatingProperties(InternalEntityEntry entry)
private List<IProperty> FindCandidatePropagatingProperties(InternalEntityEntry entry)
{
foreach (var property in ((EntityType)entry.EntityType).GetProperties())
if (!_entityTypePropagatingPropertiesMap.TryGetValue(entry.EntityType, out var candidateProperties))
{
if (property.IsForeignKey()
&& entry.HasDefaultValue(property))
candidateProperties = new List<IProperty>();
foreach (var property in entry.EntityType.GetProperties())
{
yield return property;
if (property.IsForeignKey())
{
candidateProperties.Add(property);
}
}

_entityTypePropagatingPropertiesMap[entry.EntityType] = candidateProperties;
}

return candidateProperties;
}

private static IEnumerable<IProperty> FindGeneratingProperties(InternalEntityEntry entry, bool includePKs = true)
private List<IProperty> FindCandidateGeneratingProperties(InternalEntityEntry entry)
{
foreach (var property in ((EntityType)entry.EntityType).GetProperties())
if (!_entityTypeGeneratingPropertiesMap.TryGetValue(entry.EntityType, out var candidateProperties))
{
if (property.RequiresValueGenerator()
&& entry.HasDefaultValue(property)
&& (includePKs
|| !property.IsPrimaryKey()))
candidateProperties = new List<IProperty>();
foreach (var property in entry.EntityType.GetProperties())
{
yield return property;
if (property.RequiresValueGenerator())
{
candidateProperties.Add(property);
}
}

_entityTypeGeneratingPropertiesMap[entry.EntityType] = candidateProperties;
}

return candidateProperties;
}

private ValueGenerator GetValueGenerator(InternalEntityEntry entry, IProperty property)
Expand Down

0 comments on commit 97e2366

Please sign in to comment.