From bcc79108a67a4e319e3d3d721f226942370c5bcf Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 15 Dec 2020 13:01:31 -0800 Subject: [PATCH] Use IUpdateEntry.EntityState instead of ModificationCommand.EntityState for entities using table splitting. Fixes #23668 --- .../Update/Internal/CommandBatchPreparer.cs | 10 +-- .../SqlServerEndToEndTest.cs | 64 +++++++++++++++++++ .../TPTTableSplittingSqlServerTest.cs | 20 +++--- 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index 5b4eedd49bd..9cf1ec3b491 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -492,7 +492,7 @@ private Dictionary> CreateKeyValuePred .Where(c => c.PrincipalTable.Name == command.TableName && c.PrincipalTable.Schema == command.Schema); if (!constraints.Any() - || (command.EntityState == EntityState.Modified + || (entry.EntityState == EntityState.Modified && !foreignKey.PrincipalKey.Properties.Any(p => entry.IsModified(p)))) { continue; @@ -527,7 +527,7 @@ private Dictionary> CreateKeyValuePred .Where(c => c.Table.Name == command.TableName && c.Table.Schema == command.Schema); if (!constraints.Any() - || (command.EntityState == EntityState.Modified + || (entry.EntityState == EntityState.Modified && !foreignKey.Properties.Any(p => entry.IsModified(p)))) { continue; @@ -573,7 +573,7 @@ private void AddForeignKeyEdges( { if (!foreignKey.GetMappedConstraints() .Any(c => c.Table.Name == command.TableName && c.Table.Schema == command.Schema) - || (command.EntityState == EntityState.Modified + || (entry.EntityState == EntityState.Modified && !foreignKey.Properties.Any(p => entry.IsModified(p)))) { continue; @@ -659,7 +659,7 @@ private void AddUniqueValueEdges(Multigraph c var entry = command.Entries[entryIndex]; foreach (var index in entry.EntityType.GetIndexes().Where(i => i.IsUnique && i.GetMappedTableIndexes().Any())) { - if (command.EntityState == EntityState.Modified + if (entry.EntityState == EntityState.Modified && !index.Properties.Any(p => entry.IsModified(p))) { continue; @@ -720,7 +720,7 @@ private void AddUniqueValueEdges(Multigraph c { foreach (var index in entry.EntityType.GetIndexes().Where(i => i.IsUnique && i.GetMappedTableIndexes().Any())) { - if (command.EntityState == EntityState.Modified + if (entry.EntityState == EntityState.Modified && !index.Properties.Any(p => entry.IsModified(p))) { continue; diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs index b46df8c010e..c74601e32e6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs @@ -329,6 +329,70 @@ private class BNum public string TheWalrus { get; set; } } + [ConditionalFact] + public void Can_insert_non_owner_principal_for_owned() + { + using var testDatabase = SqlServerTestStore.CreateInitialized(DatabaseName); + + var options = Fixture.CreateOptions(testDatabase); + using (var context = new FileContext(options)) + { + context.Database.EnsureCreatedResiliently(); + + var category = new Category { }; + context.Categories.Add(category); + + context.SaveChanges(); + + var fileMetadata = new FileMetadata { }; + context.FileMetadata.Add(fileMetadata); + category.Picture = new FileSource { FileId = fileMetadata.Id }; + + context.SaveChanges(); + } + } + + private class FileContext : DbContext + { + public FileContext(DbContextOptions options) + : base(options) + { + } + + public DbSet FileMetadata { get; set; } + public DbSet Categories { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(builder => + { + builder.OwnsOne(x => x.Picture, fileSource => + { + fileSource.HasOne().WithOne().HasForeignKey(x => x.FileId); + }); + }); + } + } + + private sealed class FileMetadata + { + public Guid Id { get; set; } + } + + private sealed class Category + { + public Guid Id { get; set; } + + public FileSource Picture { get; set; } + } + + private sealed class FileSource + { + public Guid? FileId { get; set; } + } + [ConditionalFact] public async Task Can_insert_TPT_dependents_with_identity() { diff --git a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs index 8f83e1263d1..413ef2518ac 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs @@ -179,22 +179,22 @@ public override void Can_change_dependent_instance_non_derived() { base.Can_change_dependent_instance_non_derived(); AssertSql( - @"@p0='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) -@p1='Repair' (Size = 4000) - -SET NOCOUNT ON; -INSERT INTO [LicensedOperators] ([VehicleName], [LicenseType]) -VALUES (@p0, @p1);", - // - @"@p1='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) + @"@p1='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) @p0='repairman' (Size = 4000) SET NOCOUNT ON; UPDATE [Vehicles] SET [Operator_Name] = @p0 WHERE [Name] = @p1; SELECT @@ROWCOUNT;", - // - @"SELECT TOP(2) [v].[Name], [v].[SeatingCapacity], [c].[AttachedVehicleName], CASE + // + @"@p2='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) +@p3='Repair' (Size = 4000) + +SET NOCOUNT ON; +INSERT INTO [LicensedOperators] ([VehicleName], [LicenseType]) +VALUES (@p2, @p3);", + // + @"SELECT TOP(2) [v].[Name], [v].[SeatingCapacity], [c].[AttachedVehicleName], CASE WHEN [c].[Name] IS NOT NULL THEN N'CompositeVehicle' WHEN [p].[Name] IS NOT NULL THEN N'PoweredVehicle' END AS [Discriminator], [t0].[Name], [t0].[Operator_Name], [t0].[LicenseType], [t0].[Discriminator]