Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix ValueGenerated On Add Or Update, , make ValueGeneratedOnUpdate() and ValueGeneratedOnAddOrUpdate() working #26713

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal
{
#pragma warning disable EF1001 // Internal EF Core API usage.

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// 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 class RelationalValueGenerationManager : ValueGenerationManager
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// 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 RelationalValueGenerationManager(
IValueGeneratorSelector valueGeneratorSelector,
IKeyPropagator keyPropagator,
IDiagnosticsLogger<DbLoggerCategory.ChangeTracking> logger,
ILoggingOptions loggingOptions) : base(valueGeneratorSelector, keyPropagator, logger, loggingOptions)
{
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// 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 override void Generate(InternalEntityEntry entry, bool includePrimaryKey = true)
{
var entityEntry = new EntityEntry(entry);

foreach (var property in entry.EntityType.GetValueGeneratingProperties())
{
if (entry.EntityState == EntityState.Added || entry.EntityState == EntityState.Detached)
{
if (
!entry.HasDefaultValue(property) ||
(!includePrimaryKey && property.IsPrimaryKey()) ||
(!property.IsKey() && !property.ValueGenerated.ForAdd() && property.GetValueGeneratorFactory() == null)
)
{
continue;
}
}
else if (entry.EntityState == EntityState.Modified || entry.EntityState == EntityState.Unchanged)
{
if (
entry.HasDefaultValue(property) ||
property.GetAnnotations().Any(m => m.Name == RelationalAnnotationNames.DefaultValueSql || m.Name == RelationalAnnotationNames.DefaultValue || m.Name == RelationalAnnotationNames.ComputedColumnSql) ||
(!includePrimaryKey && (property.IsPrimaryKey() || property.IsForeignKey())) ||
!property.ValueGenerated.ForUpdate()
)
{
continue;
}
}

var valueGenerator = GetValueGenerator(entry, property);

var generatedValue = valueGenerator.Next(entityEntry);
var temporary = valueGenerator.GeneratesTemporaryValues;

Log(entry, property, generatedValue, temporary);

SetGeneratedValue(entry, property, generatedValue, temporary);

MarkKeyUnknown(entry, includePrimaryKey, property, valueGenerator);
}
}


/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// 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 override async Task GenerateAsync(
InternalEntityEntry entry,
bool includePrimaryKey = true,
CancellationToken cancellationToken = default)
{
var entityEntry = new EntityEntry(entry);

foreach (var property in entry.EntityType.GetValueGeneratingProperties())
{
if (entry.EntityState == EntityState.Added || entry.EntityState == EntityState.Detached)
{
if (
!entry.HasDefaultValue(property) ||
(!includePrimaryKey && property.IsPrimaryKey()) ||
(!property.IsKey() && !property.ValueGenerated.ForAdd() && property.GetValueGeneratorFactory() == null)
)
{
continue;
}
}
if (entry.EntityState == EntityState.Modified || entry.EntityState == EntityState.Unchanged)
{
if (
entry.HasDefaultValue(property) ||
property.GetAnnotations().Any(m => m.Name == RelationalAnnotationNames.DefaultValueSql || m.Name == RelationalAnnotationNames.DefaultValue || m.Name == RelationalAnnotationNames.ComputedColumnSql) ||
(!includePrimaryKey && (property.IsPrimaryKey() || property.IsForeignKey())) ||
!property.ValueGenerated.ForUpdate()
)
{
continue;
}
}

var valueGenerator = GetValueGenerator(entry, property);
var generatedValue = await valueGenerator.NextAsync(entityEntry, cancellationToken)
.ConfigureAwait(false);
var temporary = valueGenerator.GeneratesTemporaryValues;

Log(entry, property, generatedValue, temporary);

SetGeneratedValue(
entry,
property,
generatedValue,
temporary);

MarkKeyUnknown(entry, includePrimaryKey, property, valueGenerator);
}
}
}

#pragma warning restore EF1001 // Internal EF Core API usage.
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.Migrations.Internal;
using Microsoft.EntityFrameworkCore.Query.Internal;
Expand Down Expand Up @@ -136,6 +137,9 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IMigrationsAssembly, MigrationsAssembly>();
TryAdd<IDatabase, RelationalDatabase>();
TryAdd<IBatchExecutor, BatchExecutor>();
#pragma warning disable EF1001 // Internal EF Core API usage.
TryAdd<IValueGenerationManager, RelationalValueGenerationManager>();
#pragma warning restore EF1001 // Internal EF Core API usage.
TryAdd<IValueGeneratorSelector, RelationalValueGeneratorSelector>();
TryAdd<IRelationalCommandBuilderFactory, RelationalCommandBuilderFactory>();
TryAdd<IRawSqlCommandBuilder, RawSqlCommandBuilder>();
Expand Down
84 changes: 58 additions & 26 deletions src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,13 @@ EntityState IUpdateEntry.EntityState
EntityState? forceStateWhenUnknownKey = null)
{
var oldState = _stateData.EntityState;
var adding = PrepareForAdd(entityState);
var addingOrUpdating = PrepareForAddOrUpdate(entityState);

entityState = PropagateToUnknownKey(oldState, entityState, adding, forceStateWhenUnknownKey);
entityState = PropagateToUnknownKey(oldState, entityState, addingOrUpdating, forceStateWhenUnknownKey);

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

SetEntityState(oldState, entityState, acceptChanges, modifyProperties);
Expand All @@ -166,13 +166,13 @@ EntityState IUpdateEntry.EntityState
CancellationToken cancellationToken = default)
{
var oldState = _stateData.EntityState;
var adding = PrepareForAdd(entityState);
var addingOrUpdating = PrepareForAddOrUpdate(entityState);

entityState = PropagateToUnknownKey(oldState, entityState, adding, forceStateWhenUnknownKey);
entityState = PropagateToUnknownKey(oldState, entityState, addingOrUpdating, forceStateWhenUnknownKey);

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

Expand All @@ -187,7 +187,7 @@ await StateManager.ValueGenerationManager.GenerateAsync(this, includePrimaryKey:
{
var keyUnknown = IsKeyUnknown;

if (adding
if (entityState == EntityState.Added
|| (oldState == EntityState.Detached
&& keyUnknown))
{
Expand All @@ -208,26 +208,58 @@ await StateManager.ValueGenerationManager.GenerateAsync(this, includePrimaryKey:
return entityState;
}

private bool PrepareForAdd(EntityState newState)
private bool PrepareForAddOrUpdate(EntityState newState)
{
if (newState != EntityState.Added
|| EntityState == EntityState.Added)
{
return false;
}

if (EntityState == EntityState.Modified)
switch (newState)
{
_stateData.FlagAllProperties(
EntityType.PropertyCount(), PropertyFlag.Modified,
flagged: false);
}
case EntityState.Added:
//If old state is Added,so return false to skip PropagateToUnknownKey And ValueGeneration,don't need duplicate.
if (EntityState == EntityState.Added || EntityState == EntityState.Modified || EntityState == EntityState.Unchanged)
{
return false;
}
// Temporarily change the internal state to unknown so that key generation, including setting key values
// can happen without constraints on changing read-only values kicking in
_stateData.EntityState = EntityState.Detached;
return true;
case EntityState.Modified:
if (EntityState == EntityState.Modified)
{
//If old state is Modified,it's always true.
return true;
}
if (EntityState == EntityState.Unchanged)
{
return true;
}

// Temporarily change the internal state to unknown so that key generation, including setting key values
// can happen without constraints on changing read-only values kicking in
_stateData.EntityState = EntityState.Detached;
return false;
case EntityState.Unchanged:

return true;
if (EntityState == EntityState.Modified)
{
return true;
}
if (EntityState == EntityState.Detached)
{
return true;
}
return false;
case EntityState.Detached:
if (EntityState == EntityState.Modified)
{
_stateData.FlagAllProperties(
EntityType.PropertyCount(), PropertyFlag.Modified,
flagged: false);
}
// Temporarily change the internal state to unknown so that key generation, including setting key values
// can happen without constraints on changing read-only values kicking in
_stateData.EntityState = EntityState.Detached;
return true;
case EntityState.Deleted:
default:
return false;
}
}

private void SetEntityState(EntityState oldState, EntityState newState, bool acceptChanges, bool modifyProperties)
Expand Down Expand Up @@ -1883,7 +1915,7 @@ public EntityEntry ToEntityEntry()
break;
case NotifyCollectionChangedAction.Reset:
throw new InvalidOperationException(CoreStrings.ResetNotSupported);
// Note: ignoring Move since index not important
// Note: ignoring Move since index not important

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong formatting?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Saibamen Not sure,but this src file auto-format by vs after last marge

}
}
}
Expand Down
Loading