Skip to content

Perf: Cache candidate properties for propagation and generation #22275

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 1 commit into from
Aug 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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