diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
index c47d9ab804c..f3ab223c6ae 100644
--- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
+++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
@@ -639,6 +639,15 @@ protected virtual void MarkShadowPropertiesNotSet([NotNull] IEntityType entityTy
}
}
+ ///
+ /// 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.
+ ///
+ public virtual void MarkUnknown([NotNull] IProperty property)
+ => _stateData.FlagProperty(property.GetIndex(), PropertyFlag.Unknown, true);
+
internal static readonly MethodInfo ReadShadowValueMethod
= typeof(InternalEntityEntry).GetTypeInfo().GetDeclaredMethod(nameof(ReadShadowValue));
@@ -1286,6 +1295,13 @@ public virtual InternalEntityEntry PrepareToSave()
property.Name,
EntityType.DisplayName()));
}
+
+ if (property.IsKey()
+ && property.IsForeignKey()
+ && _stateData.IsPropertyFlagged(property.GetIndex(), PropertyFlag.Unknown))
+ {
+ throw new InvalidOperationException(CoreStrings.UnknownKeyValue(entityType.DisplayName(), property.Name));
+ }
}
}
else if (EntityState == EntityState.Modified)
diff --git a/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs b/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs
index 90c96e200dc..867b36606bc 100644
--- a/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs
+++ b/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs
@@ -71,6 +71,8 @@ public virtual InternalEntityEntry PropagateValue(InternalEntityEntry entry, IPr
{
entry[property] = value;
}
+
+ entry.MarkUnknown(property);
}
}
@@ -108,6 +110,8 @@ public virtual async Task PropagateValueAsync(
{
entry[property] = value;
}
+
+ entry.MarkUnknown(property);
}
}
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index 85f83f0a9b1..a11d53abd23 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -514,6 +514,14 @@ public static string TempValuePersists([CanBeNull] object property, [CanBeNull]
GetString("TempValuePersists", nameof(property), nameof(entityType), nameof(state)),
property, entityType, state);
+ ///
+ /// The value of '{entityType}.{property}' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.
+ ///
+ public static string UnknownKeyValue([CanBeNull] object entityType, [CanBeNull] object property)
+ => string.Format(
+ GetString("UnknownKeyValue", nameof(entityType), nameof(property)),
+ entityType, property);
+
///
/// The EF.Property<T> method may only be used within LINQ queries.
///
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index d73698fd7fb..7d82bbd8f5e 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -322,6 +322,9 @@
The property '{property}' on entity type '{entityType}' has a temporary value while attempting to change the entity's state to '{state}'. Either set a permanent value explicitly or ensure that the database is configured to generate values for this property.
+
+ The value of '{entityType}.{property}' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.
+
An exception occurred while iterating over the results of a query for context type '{contextType}'.{newline}{error}
Error CoreEventId.QueryIterationFailed Type string Exception
diff --git a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs
index 39f52e7dd88..2196b49a9c6 100644
--- a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs
+++ b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs
@@ -26,6 +26,57 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking
{
public class ChangeTrackerTest
{
+ [ConditionalTheory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task Keys_generated_on_behalf_of_a_principal_are_not_saved(bool async)
+ {
+ using var context = new WeakHerosContext();
+
+ var entity = new Weak { Id = Guid.NewGuid() };
+
+ if (async)
+ {
+ await context.AddAsync(entity);
+ }
+ else
+ {
+ context.Add(entity);
+ }
+
+ Assert.Equal(
+ CoreStrings.UnknownKeyValue(nameof(Weak), nameof(Weak.HeroId)),
+ Assert.Throws(() => context.SaveChanges()).Message);
+ }
+
+ public class Hero
+ {
+ public Guid Id { get; set; }
+ public ICollection Weaks { get; set; }
+ }
+
+ public class Weak
+ {
+ public Guid Id { get; set; }
+ public Guid HeroId { get; set; }
+
+ public Hero Hero { get; set; }
+ }
+
+ public class WeakHerosContext : DbContext
+ {
+ protected internal override void OnModelCreating(ModelBuilder modelBuilder)
+ => modelBuilder.Entity(
+ b =>
+ {
+ b.HasKey(e => new { e.Id, e.HeroId });
+ b.HasOne(e => e.Hero).WithMany(e => e.Weaks).HasForeignKey(e => e.HeroId);
+ });
+
+ protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder.UseInMemoryDatabase(nameof(WeakHerosContext));
+ }
+
[ConditionalFact]
public void DetectChanges_is_logged()
{