diff --git a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs index 036729feee6..853a48d8c08 100644 --- a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs @@ -108,9 +108,7 @@ public static void SetDatabaseName(this IMutableIndex index, string? name) /// The index. /// The index filter expression. public static string? GetFilter(this IReadOnlyIndex index) - => (index is RuntimeIndex) - ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) - : (string?)index.FindAnnotation(RelationalAnnotationNames.Filter)?.Value; + => (string?)index.FindAnnotation(RelationalAnnotationNames.Filter)?.Value; /// /// Returns the index filter expression. @@ -120,11 +118,6 @@ public static void SetDatabaseName(this IMutableIndex index, string? name) /// The index filter expression. public static string? GetFilter(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject) { - if (index is RuntimeIndex) - { - throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); - } - var annotation = index.FindAnnotation(RelationalAnnotationNames.Filter); if (annotation != null) { diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index 85cffeec6fd..4a0eafeb4c9 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -433,7 +433,10 @@ protected override void ProcessIndexAnnotations( { base.ProcessIndexAnnotations(annotations, index, runtimeIndex, runtime); - annotations.Remove(runtime ? RelationalAnnotationNames.TableIndexMappings : RelationalAnnotationNames.Filter); + if (runtime) + { + annotations.Remove(RelationalAnnotationNames.TableIndexMappings); + } } /// diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index 9883b75e8a6..f668f2b3ae6 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -306,7 +306,11 @@ public virtual IReadOnlyList> TopologicalSort AddSameTableEdges(_modificationCommandGraph); - return _modificationCommandGraph.BatchingTopologicalSort(static (_, _, edges) => edges.All(e => e is ITable), FormatCycle); + return _modificationCommandGraph.BatchingTopologicalSort( + static (_, _, edges) => edges.All(e => + e is ITable + || (e is ITableIndex index && index.Filter != null)), + FormatCycle); } private string FormatCycle( diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index a3f20003b8e..257cff9a12a 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -1870,9 +1870,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal("AlternateIndex", alternateIndex.Name); Assert.Equal("AIX", alternateIndex.GetDatabaseName()); Assert.Null(alternateIndex[RelationalAnnotationNames.Filter]); - Assert.Equal( - CoreStrings.RuntimeModelMissingData, - Assert.Throws(() => alternateIndex.GetFilter()).Message); + Assert.Null(alternateIndex.GetFilter()); Assert.Equal(new[] { compositeIndex, alternateIndex }, principalAlternateId.GetContainingIndexes()); diff --git a/test/EFCore.Relational.Specification.Tests/UpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/UpdatesRelationalTestBase.cs index f14a6dbed80..2553227b87e 100644 --- a/test/EFCore.Relational.Specification.Tests/UpdatesRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/UpdatesRelationalTestBase.cs @@ -108,6 +108,45 @@ public virtual void Save_with_shared_foreign_key() }); } + [ConditionalFact] + public virtual void Swap_filtered_unique_index_values() + { + var productId1 = new Guid("984ade3c-2f7b-4651-a351-642e92ab7146"); + var productId2 = new Guid("0edc9136-7eed-463b-9b97-bdb9648ab877"); + + ExecuteWithStrategyInTransaction( + context => + { + var product1 = context.Products.Find(productId1)!; + var product2 = context.Products.Find(productId2)!; + + product2.Name = null; + product2.Price = product1.Price; + + context.SaveChanges(); + }, + context => + { + var product1 = context.Products.Find(productId1)!; + var product2 = context.Products.Find(productId2)!; + + product2.Name = product1.Name; + product1.Name = null; + + context.SaveChanges(); + }, + context => + { + var product1 = context.Products.Find(productId1)!; + var product2 = context.Products.Find(productId2)!; + + Assert.Equal(1.49M, product1.Price); + Assert.Null(product1.Name); + Assert.Equal(1.49M, product2.Price); + Assert.Equal("Apple Cider", product2.Name); + }); + } + [ConditionalFact] public abstract void Identifiers_are_generated_correctly(); @@ -133,6 +172,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity().HasBaseType((string)null).ToView("ProductView").ToTable("ProductTable"); modelBuilder.Entity().HasBaseType((string)null).ToView("ProductTable"); + modelBuilder.Entity().HasIndex(p => new { p.Name, p.Price }).IsUnique().HasFilter("Name IS NOT NULL"); + modelBuilder .Entity< LoginEntityTypeWithAnExtremelyLongAndOverlyConvolutedNameThatIsUsedToVerifyThatTheStoreIdentifierGenerationLengthLimitIsWorkingCorrectlyDetails