From 3c428f79994871261af61a1c8014bc95a77d4cec Mon Sep 17 00:00:00 2001 From: WhatzGames Date: Thu, 15 Aug 2024 02:28:14 +0200 Subject: [PATCH 1/6] added extensions for postgres multicolumn foreign key match --- .../NpgsqlForeignKeyBuilderExtensions.cs | 62 +++++++++++++++++++ .../NpgsqlForeignKeyExtensions.cs | 23 +++++++ .../Internal/NpgsqlAnnotationNames.cs | 8 +++ src/EFCore.PG/Metadata/PostgresMatch.cs | 23 +++++++ 4 files changed, 116 insertions(+) create mode 100644 src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs create mode 100644 src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs create mode 100644 src/EFCore.PG/Metadata/PostgresMatch.cs diff --git a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs new file mode 100644 index 0000000000..422abd1e32 --- /dev/null +++ b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs @@ -0,0 +1,62 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore; + +/// +/// Npgsql specific extension methods for configuring foreign keys. +/// +public static class NpgsqlForeignKeyBuilderExtensions +{ + /// + /// Configure the matching strategy to be used with the multicolumn foreign keys. + /// + /// The builder for the foreign key being configured. + /// The defining the used matching strategy. + /// + /// + /// + public static ReferenceReferenceBuilder HasMatchType(this ReferenceReferenceBuilder builder, PostgresMatchType matchType){ + builder.Metadata.SetMatchType(matchType); + return builder; + } + + /// + /// Configure the matching strategy to be used with the multicolumn foreign keys. + /// + /// The builder for the foreign key being configured. + /// The defining the used matching strategy. + /// + /// + /// + public static ReferenceReferenceBuilder HasMatchType(this ReferenceReferenceBuilder builder, PostgresMatchType matchType) + where TEntity : class + where TRelatedEntity : class + => (ReferenceReferenceBuilder)HasMatchType((ReferenceReferenceBuilder)builder, matchType); + + /// + /// Configure the matching strategy to be used with the multicolumn foreign keys. + /// + /// The builder for the foreign key being configured. + /// The defining the used matching strategy. + /// + /// + /// + public static ReferenceCollectionBuilder HasMatchType(this ReferenceCollectionBuilder builder, PostgresMatchType matchType){ + builder.Metadata.SetMatchType(matchType); + return builder; + } + + /// + /// Configure the matching strategy to be used with the multicolumn foreign keys. + /// + /// The builder for the foreign key being configured. + /// The defining the used matching strategy. + /// + /// + /// + public static ReferenceCollectionBuilder HasMatchType(this ReferenceCollectionBuilder builder, PostgresMatchType matchType) + where TEntity : class + where TRelatedEntity : class + => (ReferenceCollectionBuilder)HasMatchType((ReferenceCollectionBuilder)builder, matchType); +} \ No newline at end of file diff --git a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs new file mode 100644 index 0000000000..3e5d446645 --- /dev/null +++ b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs @@ -0,0 +1,23 @@ + +// ReSharper disable once CheckNamespace +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Npgsql specific extension methods for . +/// +public static class NpgsqlForeignKeyExtensions +{ + /// + /// Sets the for a multicolumn foreign key. + /// + /// the foreign key being configured. + /// the defining the used matching strategy. + /// + /// + /// + public static void SetMatchType(this IMutableForeignKey foreignKey, PostgresMatchType matchType) + => foreignKey.SetOrRemoveAnnotation(NpgsqlAnnotationNames.MatchType, matchType); +} \ No newline at end of file diff --git a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs index 0f2dd325ef..81c9436bf0 100644 --- a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs +++ b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs @@ -271,4 +271,12 @@ public static class NpgsqlAnnotationNames /// // Replaced by IsDescending in EF Core 7.0 public const string IndexSortOrder = Prefix + "IndexSortOrder"; + + /// + /// 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 const string MatchType = Prefix + "MatchType"; } diff --git a/src/EFCore.PG/Metadata/PostgresMatch.cs b/src/EFCore.PG/Metadata/PostgresMatch.cs new file mode 100644 index 0000000000..bbb7cc3f92 --- /dev/null +++ b/src/EFCore.PG/Metadata/PostgresMatch.cs @@ -0,0 +1,23 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +/// +/// Matching strategies for a multicolumn foreign key. +/// +/// +/// +/// +public enum PostgresMatchType +{ + /// + /// The default matching strategy, allows any foreignkey column to be NULL. + /// + Simple = 0, + /// + /// Currently not implemented in PostgreSQL. + /// + Partial = 1, + /// + /// Requires the foreign key to either have all columns to be set or all columns to be NULL. + /// + Full = 2 +} \ No newline at end of file From 74a2fdf0d9bae837aaa314a291a5f136584fb790 Mon Sep 17 00:00:00 2001 From: WhatzGames Date: Fri, 16 Aug 2024 01:02:56 +0200 Subject: [PATCH 2/6] added match sql translation and covering test --- .../NpgsqlForeignKeyBuilderExtensions.cs | 32 ++++++------ .../NpgsqlForeignKeyExtensions.cs | 14 ++++-- .../Internal/NpgsqlAnnotationProvider.cs | 21 ++++++++ ...tgresMatch.cs => PostgresMatchStrategy.cs} | 4 +- .../NpgsqlMigrationsSqlGenerator.cs | 49 +++++++++++++++++++ src/Shared/Check.cs | 12 +++++ .../Migrations/MigrationsNpgsqlTest.cs | 43 ++++++++++++++++ 7 files changed, 156 insertions(+), 19 deletions(-) rename src/EFCore.PG/Metadata/{PostgresMatch.cs => PostgresMatchStrategy.cs} (87%) diff --git a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs index 422abd1e32..f10c025bff 100644 --- a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs +++ b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs @@ -9,54 +9,58 @@ namespace Microsoft.EntityFrameworkCore; public static class NpgsqlForeignKeyBuilderExtensions { /// - /// Configure the matching strategy to be used with the multicolumn foreign keys. + /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// /// /// - public static ReferenceReferenceBuilder HasMatchType(this ReferenceReferenceBuilder builder, PostgresMatchType matchType){ + public static ReferenceReferenceBuilder UsesMatchStrategy(this ReferenceReferenceBuilder builder, PostgresMatchStrategy matchType){ + Check.NotNull(builder, nameof(builder)); + Check.IsDefined(matchType); builder.Metadata.SetMatchType(matchType); return builder; } /// - /// Configure the matching strategy to be used with the multicolumn foreign keys. + /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// /// /// - public static ReferenceReferenceBuilder HasMatchType(this ReferenceReferenceBuilder builder, PostgresMatchType matchType) + public static ReferenceReferenceBuilder UsesMatchStrategy(this ReferenceReferenceBuilder builder, PostgresMatchStrategy matchType) where TEntity : class where TRelatedEntity : class - => (ReferenceReferenceBuilder)HasMatchType((ReferenceReferenceBuilder)builder, matchType); + => (ReferenceReferenceBuilder)UsesMatchStrategy((ReferenceReferenceBuilder)builder, matchType); /// - /// Configure the matching strategy to be used with the multicolumn foreign keys. + /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// /// /// - public static ReferenceCollectionBuilder HasMatchType(this ReferenceCollectionBuilder builder, PostgresMatchType matchType){ + public static ReferenceCollectionBuilder UsesMatchStrategy(this ReferenceCollectionBuilder builder, PostgresMatchStrategy matchType){ + Check.NotNull(builder, nameof(builder)); + Check.IsDefined(matchType); builder.Metadata.SetMatchType(matchType); return builder; } /// - /// Configure the matching strategy to be used with the multicolumn foreign keys. + /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// /// /// - public static ReferenceCollectionBuilder HasMatchType(this ReferenceCollectionBuilder builder, PostgresMatchType matchType) + public static ReferenceCollectionBuilder UsesMatchStrategy(this ReferenceCollectionBuilder builder, PostgresMatchStrategy matchType) where TEntity : class where TRelatedEntity : class - => (ReferenceCollectionBuilder)HasMatchType((ReferenceCollectionBuilder)builder, matchType); + => (ReferenceCollectionBuilder)UsesMatchStrategy((ReferenceCollectionBuilder)builder, matchType); } \ No newline at end of file diff --git a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs index 3e5d446645..ecd9851089 100644 --- a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs +++ b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs @@ -11,13 +11,21 @@ namespace Microsoft.EntityFrameworkCore; public static class NpgsqlForeignKeyExtensions { /// - /// Sets the for a multicolumn foreign key. + /// Sets the for a foreign key. /// /// the foreign key being configured. - /// the defining the used matching strategy. + /// the defining the used matching strategy. /// /// /// - public static void SetMatchType(this IMutableForeignKey foreignKey, PostgresMatchType matchType) + public static void SetMatchType(this IMutableForeignKey foreignKey, PostgresMatchStrategy matchType) => foreignKey.SetOrRemoveAnnotation(NpgsqlAnnotationNames.MatchType, matchType); + + /// + /// Returns the assigned MATCH strategy for the provided foreign key + /// + /// the foreign key + /// the if assigned, null otherwise + public static PostgresMatchStrategy? GetMatchType(this IReadOnlyForeignKey foreignKey) => + (PostgresMatchStrategy?)foreignKey[NpgsqlAnnotationNames.MatchType]; } \ No newline at end of file diff --git a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs index a9493d6aeb..d9ebff0451 100644 --- a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs +++ b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs @@ -200,6 +200,27 @@ public override IEnumerable For(ITableIndex index, bool designTime) } } + /// + /// 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 override IEnumerable For(IForeignKeyConstraint foreignKey, bool designTime){ + if (!designTime) + { + yield break; + } + + foreach (var item in foreignKey.MappedForeignKeys) + { + if (item.GetMatchType() is {} match) + { + yield return new Annotation(NpgsqlAnnotationNames.MatchType, match); + } + } + } + /// /// 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 diff --git a/src/EFCore.PG/Metadata/PostgresMatch.cs b/src/EFCore.PG/Metadata/PostgresMatchStrategy.cs similarity index 87% rename from src/EFCore.PG/Metadata/PostgresMatch.cs rename to src/EFCore.PG/Metadata/PostgresMatchStrategy.cs index bbb7cc3f92..5e5bc66488 100644 --- a/src/EFCore.PG/Metadata/PostgresMatch.cs +++ b/src/EFCore.PG/Metadata/PostgresMatchStrategy.cs @@ -1,12 +1,12 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; /// -/// Matching strategies for a multicolumn foreign key. +/// Matching strategies for a foreign key. /// /// /// /// -public enum PostgresMatchType +public enum PostgresMatchStrategy { /// /// The default matching strategy, allows any foreignkey column to be NULL. diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 800941791a..80dcce1965 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Globalization; using System.Text; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; @@ -1447,6 +1448,54 @@ protected virtual void GenerateDropRange(PostgresRange rangeType, IModel? model, #endregion Range management + + #region MatchType management + + + /// + protected override void ForeignKeyConstraint(AddForeignKeyOperation operation, IModel? model, MigrationCommandListBuilder builder){ + if (operation.Name != null) + { + builder.Append("CONSTRAINT ").Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name)).Append(" "); + } + + builder.Append("FOREIGN KEY (").Append(ColumnList(operation.Columns)).Append(") REFERENCES ") + .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.PrincipalTable, operation.PrincipalSchema)); + if (operation.PrincipalColumns != null) + { + builder.Append(" (").Append(ColumnList(operation.PrincipalColumns)).Append(")"); + } + + if (operation[NpgsqlAnnotationNames.MatchType] is PostgresMatchStrategy matchType) + { + builder.Append(" MATCH ") + .Append(TranslateMatchType(matchType)); + + } + + if (operation.OnUpdate != 0) + { + builder.Append(" ON UPDATE "); + ForeignKeyAction(operation.OnUpdate, builder); + } + + if (operation.OnDelete != 0) + { + builder.Append(" ON DELETE "); + ForeignKeyAction(operation.OnDelete, builder); + } + } + + private string TranslateMatchType(PostgresMatchStrategy matchType) + => matchType switch { + PostgresMatchStrategy.Simple => "SIMPLE", + PostgresMatchStrategy.Partial => "PARTIAL", + PostgresMatchStrategy.Full => "FULL", + _ => throw new InvalidEnumArgumentException(nameof(matchType), (int)matchType, typeof(PostgresMatchStrategy)) + }; + + #endregion MatchType management + /// protected override void Generate( DropIndexOperation operation, diff --git a/src/Shared/Check.cs b/src/Shared/Check.cs index 05c1e3ddea..e7955f1a64 100644 --- a/src/Shared/Check.cs +++ b/src/Shared/Check.cs @@ -1,4 +1,6 @@ +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using JetBrains.Annotations; namespace Microsoft.EntityFrameworkCore.Utilities; @@ -127,4 +129,14 @@ public static void DebugAssert([DoesNotReturnIf(false)] bool condition, string m [DoesNotReturn] public static void DebugFail(string message) => throw new Exception($"Check.DebugFail failed: {message}"); + + public static void IsDefined(T value, + [InvokerParameterName, CallerArgumentExpression(nameof(value))] string? parameterName = null) + where T : struct, Enum + { + if (!Enum.IsDefined(value)) + { + throw new InvalidEnumArgumentException(parameterName, Convert.ToInt32(value), typeof(T)); + } + } } diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 82918f6b8f..5237dd8acf 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -2460,6 +2460,49 @@ public override async Task Drop_check_constraint() AssertSql("""ALTER TABLE "People" DROP CONSTRAINT "CK_People_Foo";"""); } + [Theory] + [InlineData(PostgresMatchStrategy.Simple, "SIMPLE", false)] + [InlineData(PostgresMatchStrategy.Partial, "PARTIAL", true)] + [InlineData(PostgresMatchStrategy.Full, "FULL", false)] + public async Task Add_foreign_key_with_match_strategy(PostgresMatchStrategy strategy, string matchValue, bool throws) + { + Task runningTest = Test( + builder => { + builder.Entity("Customers", delegate (EntityTypeBuilder e) + { + e.Property("Id"); + e.HasKey("Id"); + e.Property("AddressId"); + }); + builder.Entity("Orders", delegate (EntityTypeBuilder e) + { + e.Property("Id"); + e.Property("CustomerId"); + }); + }, + _ => {}, + builder => { + builder.Entity("Orders") + .HasOne("Customers") + .WithMany() + .HasForeignKey("CustomerId") + .UsesMatchStrategy(strategy) + .HasConstraintName("FK_Foo"); + }, + asserter: null); + + if (throws) + { + await Assert.ThrowsAsync(() => runningTest); + }else{ + await runningTest; + } + + AssertSql( + """CREATE INDEX "IX_Orders_CustomerId" ON "Orders" ("CustomerId");""", + $"""ALTER TABLE "Orders" ADD CONSTRAINT "FK_Foo" FOREIGN KEY ("CustomerId") REFERENCES "Customers" ("Id") MATCH {matchValue} ON DELETE CASCADE;"""); + } + #endregion #region Sequence From e8bae541b760ae61d56cd21575994102bc3e5193 Mon Sep 17 00:00:00 2001 From: WhatzGames Date: Fri, 16 Aug 2024 01:12:47 +0200 Subject: [PATCH 3/6] renamed method for consistency --- src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 80dcce1965..4d06e7cb40 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -1469,7 +1469,7 @@ protected override void ForeignKeyConstraint(AddForeignKeyOperation operation, I if (operation[NpgsqlAnnotationNames.MatchType] is PostgresMatchStrategy matchType) { builder.Append(" MATCH ") - .Append(TranslateMatchType(matchType)); + .Append(TranslateMatchStrategy(matchType)); } @@ -1486,7 +1486,7 @@ protected override void ForeignKeyConstraint(AddForeignKeyOperation operation, I } } - private string TranslateMatchType(PostgresMatchStrategy matchType) + private string TranslateMatchStrategy(PostgresMatchStrategy matchType) => matchType switch { PostgresMatchStrategy.Simple => "SIMPLE", PostgresMatchStrategy.Partial => "PARTIAL", From 5b55da20346afc7adc862f3b08c827694527dd8e Mon Sep 17 00:00:00 2001 From: WhatzGames Date: Fri, 16 Aug 2024 01:31:50 +0200 Subject: [PATCH 4/6] further cleanup of namings etc. --- .../NpgsqlForeignKeyBuilderExtensions.cs | 28 +++++++++---------- .../NpgsqlForeignKeyExtensions.cs | 14 +++++----- .../Internal/NpgsqlAnnotationNames.cs | 2 +- .../Internal/NpgsqlAnnotationProvider.cs | 4 +-- .../Metadata/PostgresMatchStrategy.cs | 2 +- .../NpgsqlMigrationsSqlGenerator.cs | 14 +++++----- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs index f10c025bff..8f83506883 100644 --- a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs +++ b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs @@ -12,14 +12,14 @@ public static class NpgsqlForeignKeyBuilderExtensions /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// /// /// - public static ReferenceReferenceBuilder UsesMatchStrategy(this ReferenceReferenceBuilder builder, PostgresMatchStrategy matchType){ + public static ReferenceReferenceBuilder UsesMatchStrategy(this ReferenceReferenceBuilder builder, PostgresMatchStrategy matchStrategy){ Check.NotNull(builder, nameof(builder)); - Check.IsDefined(matchType); - builder.Metadata.SetMatchType(matchType); + Check.IsDefined(matchStrategy); + builder.Metadata.SetMatchStrategy(matchStrategy); return builder; } @@ -27,27 +27,27 @@ public static ReferenceReferenceBuilder UsesMatchStrategy(this ReferenceReferenc /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// /// /// - public static ReferenceReferenceBuilder UsesMatchStrategy(this ReferenceReferenceBuilder builder, PostgresMatchStrategy matchType) + public static ReferenceReferenceBuilder UsesMatchStrategy(this ReferenceReferenceBuilder builder, PostgresMatchStrategy matchStrategy) where TEntity : class where TRelatedEntity : class - => (ReferenceReferenceBuilder)UsesMatchStrategy((ReferenceReferenceBuilder)builder, matchType); + => (ReferenceReferenceBuilder)UsesMatchStrategy((ReferenceReferenceBuilder)builder, matchStrategy); /// /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// /// /// - public static ReferenceCollectionBuilder UsesMatchStrategy(this ReferenceCollectionBuilder builder, PostgresMatchStrategy matchType){ + public static ReferenceCollectionBuilder UsesMatchStrategy(this ReferenceCollectionBuilder builder, PostgresMatchStrategy matchStrategy){ Check.NotNull(builder, nameof(builder)); - Check.IsDefined(matchType); - builder.Metadata.SetMatchType(matchType); + Check.IsDefined(matchStrategy); + builder.Metadata.SetMatchStrategy(matchStrategy); return builder; } @@ -55,12 +55,12 @@ public static ReferenceCollectionBuilder UsesMatchStrategy(this ReferenceCollect /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// /// /// - public static ReferenceCollectionBuilder UsesMatchStrategy(this ReferenceCollectionBuilder builder, PostgresMatchStrategy matchType) + public static ReferenceCollectionBuilder UsesMatchStrategy(this ReferenceCollectionBuilder builder, PostgresMatchStrategy matchStrategy) where TEntity : class where TRelatedEntity : class - => (ReferenceCollectionBuilder)UsesMatchStrategy((ReferenceCollectionBuilder)builder, matchType); + => (ReferenceCollectionBuilder)UsesMatchStrategy((ReferenceCollectionBuilder)builder, matchStrategy); } \ No newline at end of file diff --git a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs index ecd9851089..34332dd7c5 100644 --- a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs +++ b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs @@ -14,18 +14,18 @@ public static class NpgsqlForeignKeyExtensions /// Sets the for a foreign key. /// /// the foreign key being configured. - /// the defining the used matching strategy. + /// the defining the used matching strategy. /// /// /// - public static void SetMatchType(this IMutableForeignKey foreignKey, PostgresMatchStrategy matchType) - => foreignKey.SetOrRemoveAnnotation(NpgsqlAnnotationNames.MatchType, matchType); + public static void SetMatchStrategy(this IMutableForeignKey foreignKey, PostgresMatchStrategy matchStrategy) + => foreignKey.SetOrRemoveAnnotation(NpgsqlAnnotationNames.MatchStrategy, matchStrategy); /// - /// Returns the assigned MATCH strategy for the provided foreign key + /// Returns the assigned for the provided foreign key /// /// the foreign key - /// the if assigned, null otherwise - public static PostgresMatchStrategy? GetMatchType(this IReadOnlyForeignKey foreignKey) => - (PostgresMatchStrategy?)foreignKey[NpgsqlAnnotationNames.MatchType]; + /// the if assigned, null otherwise + public static PostgresMatchStrategy? GetMatchStrategy(this IReadOnlyForeignKey foreignKey) => + (PostgresMatchStrategy?)foreignKey[NpgsqlAnnotationNames.MatchStrategy]; } \ No newline at end of file diff --git a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs index 81c9436bf0..eac083f2a1 100644 --- a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs +++ b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs @@ -278,5 +278,5 @@ public static class NpgsqlAnnotationNames /// 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 const string MatchType = Prefix + "MatchType"; + public const string MatchStrategy = Prefix + "MatchStrategy"; } diff --git a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs index d9ebff0451..82dfe2cf1e 100644 --- a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs +++ b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs @@ -214,9 +214,9 @@ public override IEnumerable For(IForeignKeyConstraint foreignKey, b foreach (var item in foreignKey.MappedForeignKeys) { - if (item.GetMatchType() is {} match) + if (item.GetMatchStrategy() is {} match) { - yield return new Annotation(NpgsqlAnnotationNames.MatchType, match); + yield return new Annotation(NpgsqlAnnotationNames.MatchStrategy, match); } } } diff --git a/src/EFCore.PG/Metadata/PostgresMatchStrategy.cs b/src/EFCore.PG/Metadata/PostgresMatchStrategy.cs index 5e5bc66488..cef62d5b39 100644 --- a/src/EFCore.PG/Metadata/PostgresMatchStrategy.cs +++ b/src/EFCore.PG/Metadata/PostgresMatchStrategy.cs @@ -9,7 +9,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; public enum PostgresMatchStrategy { /// - /// The default matching strategy, allows any foreignkey column to be NULL. + /// The default matching strategy, allows any foreign key column to be NULL. /// Simple = 0, /// diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 4d06e7cb40..90c4c03c75 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -1449,7 +1449,7 @@ protected virtual void GenerateDropRange(PostgresRange rangeType, IModel? model, #endregion Range management - #region MatchType management + #region MatchingStrategy management /// @@ -1466,10 +1466,10 @@ protected override void ForeignKeyConstraint(AddForeignKeyOperation operation, I builder.Append(" (").Append(ColumnList(operation.PrincipalColumns)).Append(")"); } - if (operation[NpgsqlAnnotationNames.MatchType] is PostgresMatchStrategy matchType) + if (operation[NpgsqlAnnotationNames.MatchStrategy] is PostgresMatchStrategy matchStrategy) { builder.Append(" MATCH ") - .Append(TranslateMatchStrategy(matchType)); + .Append(TranslateMatchStrategy(matchStrategy)); } @@ -1486,15 +1486,15 @@ protected override void ForeignKeyConstraint(AddForeignKeyOperation operation, I } } - private string TranslateMatchStrategy(PostgresMatchStrategy matchType) - => matchType switch { + private string TranslateMatchStrategy(PostgresMatchStrategy matchStrategy) + => matchStrategy switch { PostgresMatchStrategy.Simple => "SIMPLE", PostgresMatchStrategy.Partial => "PARTIAL", PostgresMatchStrategy.Full => "FULL", - _ => throw new InvalidEnumArgumentException(nameof(matchType), (int)matchType, typeof(PostgresMatchStrategy)) + _ => throw new InvalidEnumArgumentException(nameof(matchStrategy), (int)matchStrategy, typeof(PostgresMatchStrategy)) }; - #endregion MatchType management + #endregion MatchingStrategy management /// protected override void Generate( From 8fcd68b0f40b6ce72523074e37c387d4563ce61d Mon Sep 17 00:00:00 2001 From: Obed Kooijman Date: Wed, 18 Dec 2024 21:39:47 +0100 Subject: [PATCH 5/6] remove attribute and apply cleanup --- .../NpgsqlForeignKeyBuilderExtensions.cs | 38 +++++----- .../NpgsqlForeignKeyExtensions.cs | 21 +++--- .../Internal/NpgsqlAnnotationProvider.cs | 5 +- .../Metadata/PostgresMatchStrategy.cs | 6 +- .../NpgsqlMigrationsSqlGenerator.cs | 13 ++-- src/Shared/Check.cs | 6 +- .../Migrations/MigrationsNpgsqlTest.cs | 70 +++++++++++-------- 7 files changed, 89 insertions(+), 70 deletions(-) diff --git a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs index 8f83506883..69eac600bd 100644 --- a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs +++ b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlForeignKeyBuilderExtensions.cs @@ -12,13 +12,14 @@ public static class NpgsqlForeignKeyBuilderExtensions /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// - /// + /// /// - public static ReferenceReferenceBuilder UsesMatchStrategy(this ReferenceReferenceBuilder builder, PostgresMatchStrategy matchStrategy){ + public static ReferenceReferenceBuilder UsesMatchStrategy(this ReferenceReferenceBuilder builder, PostgresMatchStrategy matchStrategy) + { Check.NotNull(builder, nameof(builder)); - Check.IsDefined(matchStrategy); + Check.IsDefined(matchStrategy, nameof(matchStrategy)); builder.Metadata.SetMatchStrategy(matchStrategy); return builder; } @@ -27,26 +28,29 @@ public static ReferenceReferenceBuilder UsesMatchStrategy(this ReferenceReferenc /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// - /// + /// /// - public static ReferenceReferenceBuilder UsesMatchStrategy(this ReferenceReferenceBuilder builder, PostgresMatchStrategy matchStrategy) + public static ReferenceReferenceBuilder UsesMatchStrategy( + this ReferenceReferenceBuilder builder, + PostgresMatchStrategy matchStrategy) where TEntity : class where TRelatedEntity : class - => (ReferenceReferenceBuilder)UsesMatchStrategy((ReferenceReferenceBuilder)builder, matchStrategy); + => (ReferenceReferenceBuilder)UsesMatchStrategy((ReferenceReferenceBuilder)builder, matchStrategy); /// /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// - /// + /// /// - public static ReferenceCollectionBuilder UsesMatchStrategy(this ReferenceCollectionBuilder builder, PostgresMatchStrategy matchStrategy){ + public static ReferenceCollectionBuilder UsesMatchStrategy(this ReferenceCollectionBuilder builder, PostgresMatchStrategy matchStrategy) + { Check.NotNull(builder, nameof(builder)); - Check.IsDefined(matchStrategy); + Check.IsDefined(matchStrategy, nameof(matchStrategy)); builder.Metadata.SetMatchStrategy(matchStrategy); return builder; } @@ -55,12 +59,14 @@ public static ReferenceCollectionBuilder UsesMatchStrategy(this ReferenceCollect /// Configure the matching strategy to be used with the foreign key. /// /// The builder for the foreign key being configured. - /// The defining the used matching strategy. + /// The defining the used matching strategy. /// - /// + /// /// - public static ReferenceCollectionBuilder UsesMatchStrategy(this ReferenceCollectionBuilder builder, PostgresMatchStrategy matchStrategy) + public static ReferenceCollectionBuilder UsesMatchStrategy( + this ReferenceCollectionBuilder builder, + PostgresMatchStrategy matchStrategy) where TEntity : class where TRelatedEntity : class => (ReferenceCollectionBuilder)UsesMatchStrategy((ReferenceCollectionBuilder)builder, matchStrategy); -} \ No newline at end of file +} diff --git a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs index 34332dd7c5..cccc93295e 100644 --- a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs +++ b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlForeignKeyExtensions.cs @@ -1,31 +1,30 @@ - -// ReSharper disable once CheckNamespace using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal; +// ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore; /// -/// Npgsql specific extension methods for . +/// Npgsql specific extension methods for . /// public static class NpgsqlForeignKeyExtensions { /// - /// Sets the for a foreign key. + /// Sets the for a foreign key. /// /// the foreign key being configured. - /// the defining the used matching strategy. + /// the defining the used matching strategy. /// - /// + /// /// public static void SetMatchStrategy(this IMutableForeignKey foreignKey, PostgresMatchStrategy matchStrategy) => foreignKey.SetOrRemoveAnnotation(NpgsqlAnnotationNames.MatchStrategy, matchStrategy); /// - /// Returns the assigned for the provided foreign key + /// Returns the assigned for the provided foreign key /// /// the foreign key - /// the if assigned, null otherwise - public static PostgresMatchStrategy? GetMatchStrategy(this IReadOnlyForeignKey foreignKey) => - (PostgresMatchStrategy?)foreignKey[NpgsqlAnnotationNames.MatchStrategy]; -} \ No newline at end of file + /// the if assigned, null otherwise + public static PostgresMatchStrategy? GetMatchStrategy(this IReadOnlyForeignKey foreignKey) + => (PostgresMatchStrategy?)foreignKey[NpgsqlAnnotationNames.MatchStrategy]; +} diff --git a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs index 82dfe2cf1e..6d6c9c14c9 100644 --- a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs +++ b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs @@ -206,7 +206,8 @@ public override IEnumerable For(ITableIndex index, bool designTime) /// 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 override IEnumerable For(IForeignKeyConstraint foreignKey, bool designTime){ + public override IEnumerable For(IForeignKeyConstraint foreignKey, bool designTime) + { if (!designTime) { yield break; @@ -214,7 +215,7 @@ public override IEnumerable For(IForeignKeyConstraint foreignKey, b foreach (var item in foreignKey.MappedForeignKeys) { - if (item.GetMatchStrategy() is {} match) + if (item.GetMatchStrategy() is { } match) { yield return new Annotation(NpgsqlAnnotationNames.MatchStrategy, match); } diff --git a/src/EFCore.PG/Metadata/PostgresMatchStrategy.cs b/src/EFCore.PG/Metadata/PostgresMatchStrategy.cs index cef62d5b39..be2acec833 100644 --- a/src/EFCore.PG/Metadata/PostgresMatchStrategy.cs +++ b/src/EFCore.PG/Metadata/PostgresMatchStrategy.cs @@ -4,7 +4,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; /// Matching strategies for a foreign key. /// /// -/// +/// /// public enum PostgresMatchStrategy { @@ -12,12 +12,14 @@ public enum PostgresMatchStrategy /// The default matching strategy, allows any foreign key column to be NULL. /// Simple = 0, + /// /// Currently not implemented in PostgreSQL. /// Partial = 1, + /// /// Requires the foreign key to either have all columns to be set or all columns to be NULL. /// Full = 2 -} \ No newline at end of file +} diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 90c4c03c75..3b3ff2fe8f 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -1448,12 +1448,11 @@ protected virtual void GenerateDropRange(PostgresRange rangeType, IModel? model, #endregion Range management - #region MatchingStrategy management - - /// - protected override void ForeignKeyConstraint(AddForeignKeyOperation operation, IModel? model, MigrationCommandListBuilder builder){ + /// + protected override void ForeignKeyConstraint(AddForeignKeyOperation operation, IModel? model, MigrationCommandListBuilder builder) + { if (operation.Name != null) { builder.Append("CONSTRAINT ").Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name)).Append(" "); @@ -1470,7 +1469,6 @@ protected override void ForeignKeyConstraint(AddForeignKeyOperation operation, I { builder.Append(" MATCH ") .Append(TranslateMatchStrategy(matchStrategy)); - } if (operation.OnUpdate != 0) @@ -1486,8 +1484,9 @@ protected override void ForeignKeyConstraint(AddForeignKeyOperation operation, I } } - private string TranslateMatchStrategy(PostgresMatchStrategy matchStrategy) - => matchStrategy switch { + private static string TranslateMatchStrategy(PostgresMatchStrategy matchStrategy) + => matchStrategy switch + { PostgresMatchStrategy.Simple => "SIMPLE", PostgresMatchStrategy.Partial => "PARTIAL", PostgresMatchStrategy.Full => "FULL", diff --git a/src/Shared/Check.cs b/src/Shared/Check.cs index e7955f1a64..17082029d4 100644 --- a/src/Shared/Check.cs +++ b/src/Shared/Check.cs @@ -1,6 +1,5 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; using JetBrains.Annotations; namespace Microsoft.EntityFrameworkCore.Utilities; @@ -130,8 +129,9 @@ public static void DebugAssert([DoesNotReturnIf(false)] bool condition, string m public static void DebugFail(string message) => throw new Exception($"Check.DebugFail failed: {message}"); - public static void IsDefined(T value, - [InvokerParameterName, CallerArgumentExpression(nameof(value))] string? parameterName = null) + public static void IsDefined( + T value, + [InvokerParameterName] string parameterName) where T : struct, Enum { if (!Enum.IsDefined(value)) diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 5237dd8acf..b63d31cc3e 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -1147,7 +1147,8 @@ await Test( e.Property("Y"); }), builder => builder.Entity("People").Property("Sum") - .HasComputedColumnSql(""" + .HasComputedColumnSql( + """ "X" + "Y" """, stored: true), builder => builder.Entity("People").Property("Sum"), @@ -1671,7 +1672,8 @@ await Test( "People", b => { b.Property("Name"); - b.Property("Name2").HasComputedColumnSql(""" + b.Property("Name2").HasComputedColumnSql( + """ "Name" """, stored: true); }), @@ -1681,7 +1683,8 @@ await Test( model => { var computedColumn = Assert.Single(Assert.Single(model.Tables).Columns, c => c.Name == "Name2"); - Assert.Equal(""" + Assert.Equal( + """ "Name" """, computedColumn.ComputedColumnSql); Assert.Equal(NonDefaultCollation, computedColumn.Collation); @@ -1938,7 +1941,8 @@ await Test( _ => { }, builder => builder.Entity("People").HasIndex("Name") .IncludeProperties("FirstName", "LastName") - .HasFilter(""" + .HasFilter( + """ "Name" IS NOT NULL """), model => @@ -2031,7 +2035,8 @@ await Test( builder => builder.Entity("People").HasIndex("Name") .IsUnique() .IncludeProperties("FirstName", "LastName") - .HasFilter(""" + .HasFilter( + """ "Name" IS NOT NULL """), model => @@ -2466,41 +2471,50 @@ public override async Task Drop_check_constraint() [InlineData(PostgresMatchStrategy.Full, "FULL", false)] public async Task Add_foreign_key_with_match_strategy(PostgresMatchStrategy strategy, string matchValue, bool throws) { - Task runningTest = Test( - builder => { - builder.Entity("Customers", delegate (EntityTypeBuilder e) - { - e.Property("Id"); - e.HasKey("Id"); - e.Property("AddressId"); - }); - builder.Entity("Orders", delegate (EntityTypeBuilder e) - { - e.Property("Id"); - e.Property("CustomerId"); - }); + var runningTest = Test( + builder => + { + builder.Entity( + "Customers", delegate(EntityTypeBuilder e) + { + e.Property("Id"); + e.HasKey("Id"); + e.Property("AddressId"); + }); + builder.Entity( + "Orders", delegate(EntityTypeBuilder e) + { + e.Property("Id"); + e.Property("CustomerId"); + }); }, - _ => {}, - builder => { + _ => { }, + builder => + { builder.Entity("Orders") - .HasOne("Customers") - .WithMany() - .HasForeignKey("CustomerId") - .UsesMatchStrategy(strategy) - .HasConstraintName("FK_Foo"); + .HasOne("Customers") + .WithMany() + .HasForeignKey("CustomerId") + .UsesMatchStrategy(strategy) + .HasConstraintName("FK_Foo"); }, asserter: null); if (throws) { await Assert.ThrowsAsync(() => runningTest); - }else{ + } + else + { await runningTest; } AssertSql( """CREATE INDEX "IX_Orders_CustomerId" ON "Orders" ("CustomerId");""", - $"""ALTER TABLE "Orders" ADD CONSTRAINT "FK_Foo" FOREIGN KEY ("CustomerId") REFERENCES "Customers" ("Id") MATCH {matchValue} ON DELETE CASCADE;"""); + // + $""" +ALTER TABLE "Orders" ADD CONSTRAINT "FK_Foo" FOREIGN KEY ("CustomerId") REFERENCES "Customers" ("Id") MATCH {matchValue} ON DELETE CASCADE; +"""); } #endregion @@ -2770,7 +2784,6 @@ SELECT setval( """); } - #endregion Data seeding public override async Task Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table() @@ -3268,7 +3281,6 @@ public override async Task Add_required_primitve_collection_with_custom_converte AssertSql("""ALTER TABLE "Customers" ADD "Numbers" text NOT NULL DEFAULT 'some numbers';"""); } - protected override string NonDefaultCollation => "POSIX"; From deaa27d5df324b42618ea7b9df43dae36ae5858d Mon Sep 17 00:00:00 2001 From: Obed Kooijman Date: Wed, 18 Dec 2024 22:13:53 +0100 Subject: [PATCH 6/6] cleanup assertion format --- .../Migrations/MigrationsNpgsqlTest.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index b63d31cc3e..29db36f7c5 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -2510,7 +2510,9 @@ public async Task Add_foreign_key_with_match_strategy(PostgresMatchStrategy stra } AssertSql( - """CREATE INDEX "IX_Orders_CustomerId" ON "Orders" ("CustomerId");""", + """ +CREATE INDEX "IX_Orders_CustomerId" ON "Orders" ("CustomerId"); +""", // $""" ALTER TABLE "Orders" ADD CONSTRAINT "FK_Foo" FOREIGN KEY ("CustomerId") REFERENCES "Customers" ("Id") MATCH {matchValue} ON DELETE CASCADE;