Skip to content

Commit

Permalink
Generate All Possible Values Other Than PKs When Entities are Tracked…
Browse files Browse the repository at this point in the history
… From Detached State

Fixes #15289
  • Loading branch information
TheFanatr committed Aug 24, 2020
1 parent 6dacc0d commit 04575b0
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 10 deletions.
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);
void Generate([NotNull] InternalEntityEntry entry, bool includePKs = true);

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

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

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

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

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

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

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

Expand Down
13 changes: 8 additions & 5 deletions src/EFCore/ChangeTracking/Internal/ValueGenerationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ 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)
public virtual void Generate(InternalEntityEntry entry, bool includePKs = true)
{
var entityEntry = new EntityEntry(entry);

foreach (var property in FindGeneratingProperties(entry))
foreach (var property in FindGeneratingProperties(entry, includePKs))
{
var valueGenerator = GetValueGenerator(entry, property);

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

foreach (var property in FindGeneratingProperties(entry))
foreach (var property in FindGeneratingProperties(entry, includePKs))
{
var valueGenerator = GetValueGenerator(entry, property);
var generatedValue = await valueGenerator.NextAsync(entityEntry, cancellationToken)
Expand Down Expand Up @@ -153,12 +154,14 @@ private static IEnumerable<IProperty> FindPropagatingProperties(InternalEntityEn
}
}

private static IEnumerable<IProperty> FindGeneratingProperties(InternalEntityEntry entry)
private static IEnumerable<IProperty> FindGeneratingProperties(InternalEntityEntry entry, bool includePKs = true)
{
foreach (var property in ((EntityType)entry.EntityType).GetProperties())
{
if (property.RequiresValueGenerator()
&& entry.HasDefaultValue(property))
&& entry.HasDefaultValue(property)
&& (includePKs
|| !property.IsPrimaryKey()))
{
yield return property;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;

namespace Microsoft.EntityFrameworkCore.Internal
{
public class IdentifierShadowValuePresenceTest
{
[ConditionalFact]
public async Task Entities_with_null_PK_can_be_added_with_normal_use_of_DbContext_methods_and_have_id_shadow_value_and_PK_created()
{
await using var testDatabase = CosmosTestStore.Create("IdentifierShadowValuePresenceTest");
using var context = new IdentifierShadowValuePresenceTestContext(testDatabase);

var item = new GItem { };

Assert.Null(item.Id);

var entry = context.Add(item);

var id = entry.Property("__id").CurrentValue;

Assert.NotNull(item.Id);
Assert.NotNull(id);

Assert.Equal($"GItem|{item.Id}", id);
Assert.Equal(EntityState.Added, entry.State);
}

[ConditionalFact]
public async Task Entities_can_be_tracked_with_normal_use_of_DbContext_methods_and_have_correct_resultant_state_and_id_shadow_value()
{
await using var testDatabase = CosmosTestStore.Create("IdentifierShadowValuePresenceTest");
using var context = new IdentifierShadowValuePresenceTestContext(testDatabase);

var item = new Item { Id = 1337 };
var entry = context.Attach(item);

Assert.Equal($"Item|{item.Id}", entry.Property("__id").CurrentValue);
Assert.Equal(EntityState.Unchanged, entry.State);

entry.State = EntityState.Detached;
entry = context.Update(item = new Item { Id = 71 });

Assert.Equal($"Item|{item.Id}", entry.Property("__id").CurrentValue);
Assert.Equal(EntityState.Modified, entry.State);

entry.State = EntityState.Detached;
entry = context.Remove(item = new Item { Id = 33 });

Assert.Equal($"Item|{item.Id}", entry.Property("__id").CurrentValue);
Assert.Equal(EntityState.Deleted, entry.State);
}
}

public class IdentifierShadowValuePresenceTestContext : DbContext
{
private readonly string _connectionUri;
private readonly string _authToken;
private readonly string _name;

public IdentifierShadowValuePresenceTestContext(CosmosTestStore testStore)
{
_connectionUri = testStore.ConnectionUri;
_authToken = testStore.AuthToken;
_name = testStore.Name;
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseCosmos(
_connectionUri,
_authToken,
_name,
b => b.ApplyConfiguration());
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}

public DbSet<GItem> GItems { get; set; }

public DbSet<Item> Items { get; set; }
}

public class GItem
{
public Guid? Id { get; set; }
}

public class Item
{
public int Id { get; set; }
}
}

0 comments on commit 04575b0

Please sign in to comment.