Skip to content

Commit

Permalink
Store store-generated FK values in the store-generated snapshot
Browse files Browse the repository at this point in the history
Fixes #22603
  • Loading branch information
ajcvickers committed Sep 6, 2021
1 parent f3c82b6 commit fa6f5a6
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 11 deletions.
6 changes: 5 additions & 1 deletion src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,10 @@ public void PropagateValue(
SetTemporaryValue(dependentProperty, principalValue);
}
}
else if (principalEntry.GetValueType(principalProperty) == CurrentValueType.StoreGenerated)
{
SetStoreGeneratedValue(dependentProperty, principalValue);
}
else
{
SetProperty(dependentProperty, principalValue, isMaterialization, setModified);
Expand Down Expand Up @@ -1052,7 +1056,7 @@ public void EnsureStoreGeneratedValues()
{
if (_storeGeneratedValues.IsEmpty)
{
_storeGeneratedValues = new SidecarValues(((IRuntimeEntityType)EntityType).StoreGeneratedValuesFactory(this));
_storeGeneratedValues = new SidecarValues(((IRuntimeEntityType)EntityType).StoreGeneratedValuesFactory());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal
{
/// <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 StoreGeneratedValuesFactoryFactory : SidecarValuesFactoryFactory
{
/// <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>
protected override bool UseEntityVariable
=> false;

/// <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>
protected override Expression CreateReadShadowValueExpression(ParameterExpression parameter, IPropertyBase property)
=> Expression.Default(property.ClrType);

/// <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>
protected override Expression CreateReadValueExpression(ParameterExpression parameter, IPropertyBase property)
=> Expression.Default(property.ClrType);
}
}
6 changes: 3 additions & 3 deletions src/EFCore/Metadata/Internal/EntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private readonly SortedDictionary<string, ServiceProperty> _serviceProperties
private Func<InternalEntityEntry, ISnapshot>? _relationshipSnapshotFactory;
private Func<InternalEntityEntry, ISnapshot>? _originalValuesFactory;
private Func<InternalEntityEntry, ISnapshot>? _temporaryValuesFactory;
private Func<InternalEntityEntry, ISnapshot>? _storeGeneratedValuesFactory;
private Func<ISnapshot>? _storeGeneratedValuesFactory;
private Func<ValueBuffer, ISnapshot>? _shadowValuesFactory;
private Func<ISnapshot>? _emptyShadowValuesFactory;
private Func<MaterializationContext, object>? _instanceFactory;
Expand Down Expand Up @@ -2665,13 +2665,13 @@ public virtual Func<InternalEntityEntry, ISnapshot> OriginalValuesFactory
/// 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 Func<InternalEntityEntry, ISnapshot> StoreGeneratedValuesFactory
public virtual Func<ISnapshot> StoreGeneratedValuesFactory
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _storeGeneratedValuesFactory, this,
static entityType =>
{
entityType.EnsureReadOnly();
return new SidecarValuesFactoryFactory().Create(entityType);
return new StoreGeneratedValuesFactoryFactory().CreateEmpty(entityType);
});

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Metadata/Internal/IRuntimeEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public interface IRuntimeEntityType : IEntityType
/// 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>
Func<InternalEntityEntry, ISnapshot> StoreGeneratedValuesFactory { get; }
Func<ISnapshot> StoreGeneratedValuesFactory { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
6 changes: 3 additions & 3 deletions src/EFCore/Metadata/RuntimeEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private readonly SortedDictionary<string, RuntimeServiceProperty> _serviceProper
private Func<InternalEntityEntry, ISnapshot>? _relationshipSnapshotFactory;
private Func<InternalEntityEntry, ISnapshot>? _originalValuesFactory;
private Func<InternalEntityEntry, ISnapshot>? _temporaryValuesFactory;
private Func<InternalEntityEntry, ISnapshot>? _storeGeneratedValuesFactory;
private Func<ISnapshot>? _storeGeneratedValuesFactory;
private Func<ValueBuffer, ISnapshot>? _shadowValuesFactory;
private Func<ISnapshot>? _emptyShadowValuesFactory;
private Func<MaterializationContext, object>? _instanceFactory;
Expand Down Expand Up @@ -1258,10 +1258,10 @@ Func<InternalEntityEntry, ISnapshot> IRuntimeEntityType.OriginalValuesFactory
static entityType => new OriginalValuesFactoryFactory().Create(entityType));

/// <inheritdoc/>
Func<InternalEntityEntry, ISnapshot> IRuntimeEntityType.StoreGeneratedValuesFactory
Func<ISnapshot> IRuntimeEntityType.StoreGeneratedValuesFactory
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _storeGeneratedValuesFactory, this,
static entityType => new SidecarValuesFactoryFactory().Create(entityType));
static entityType => new StoreGeneratedValuesFactoryFactory().CreateEmpty(entityType));

/// <inheritdoc/>
Func<InternalEntityEntry, ISnapshot> IRuntimeEntityType.TemporaryValuesFactory
Expand Down
21 changes: 20 additions & 1 deletion test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -1467,6 +1468,19 @@ public int Id
}

public string Name { get; set; }

public ICollection<Species> MixedMetaphors { get; set; }
public Species Species { get; set; }
}

protected class Species
{
public int Id { get; set; }
public string Name { get; set; }

public int? DarwinId { get; set; }
public int? MetaphoricId { get; set; }

}

protected class Gumball
Expand Down Expand Up @@ -1915,7 +1929,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
property.SetAfterSaveBehavior(PropertySaveBehavior.Throw);
});

modelBuilder.Entity<Darwin>();
modelBuilder.Entity<Darwin>(
b =>
{
b.HasOne(e => e.Species).WithOne().HasForeignKey<Species>(e => e.DarwinId);
b.HasMany(e => e.MixedMetaphors).WithOne().HasForeignKey(e => e.MetaphoricId);
});

modelBuilder.Entity<WithBackingFields>(
b =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,32 @@ public virtual void Exception_in_SaveChanges_causes_store_values_to_be_reverted(
var entities = new List<Darwin>();
for (var i = 0; i < 100; i++)
{
entities.Add(new Darwin());
entities.Add(new()
{
Species = new() { Name = "Goldfish (with legs)" },
MixedMetaphors = new List<Species>
{
new() { Name = "Large ground finch" },
new() { Name = "Medium ground finch" },
new() { Name = "Small tree finch" },
new() { Name = "Green warbler-finch" }
}
});
}

entities.Add(
new Darwin { Id = 1777 });
new()
{
Id = 1777,
Species = new() { Name = "Goldfish (with legs)" },
MixedMetaphors = new List<Species>
{
new() { Name = "Large ground finch" },
new() { Name = "Medium ground finch" },
new() { Name = "Small tree finch" },
new() { Name = "Green warbler-finch" }
}
});

for (var i = 0; i < 2; i++)
{
Expand Down Expand Up @@ -77,9 +98,19 @@ public virtual void Exception_in_SaveChanges_causes_store_values_to_be_reverted(
{
Assert.Equal(0, entity.Id);
Assert.Null(entity._id);
Assert.Null(entity.Species.DarwinId);
foreach (var species in entity.MixedMetaphors)
{
Assert.Null(species.MetaphoricId);
}
}
Assert.Equal(1777, entities[100].Id);
Assert.Equal(1777, entities[100].Species.DarwinId);
foreach (var species in entities[100].MixedMetaphors)
{
Assert.Equal(1777, species.MetaphoricId);
}
foreach (var entity in entities)
{
Expand Down

0 comments on commit fa6f5a6

Please sign in to comment.