diff --git a/src/EFCore.SqlServer/Diagnostics/Internal/SqlServerLoggingDefinitions.cs b/src/EFCore.SqlServer/Diagnostics/Internal/SqlServerLoggingDefinitions.cs
index 6456e512209..12bc41e89e9 100644
--- a/src/EFCore.SqlServer/Diagnostics/Internal/SqlServerLoggingDefinitions.cs
+++ b/src/EFCore.SqlServer/Diagnostics/Internal/SqlServerLoggingDefinitions.cs
@@ -149,6 +149,14 @@ public class SqlServerLoggingDefinitions : RelationalLoggingDefinitions
///
public EventDefinitionBase? LogReflexiveConstraintIgnored;
+ ///
+ /// 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 EventDefinitionBase? LogDuplicateForeignKeyConstraintIgnored;
+
///
/// 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.SqlServer/Diagnostics/SqlServerEventId.cs b/src/EFCore.SqlServer/Diagnostics/SqlServerEventId.cs
index 9e234a59dfd..a15bcaae125 100644
--- a/src/EFCore.SqlServer/Diagnostics/SqlServerEventId.cs
+++ b/src/EFCore.SqlServer/Diagnostics/SqlServerEventId.cs
@@ -62,7 +62,8 @@ private enum Id
IndexFound,
ForeignKeyFound,
ForeignKeyPrincipalColumnMissingWarning,
- ReflexiveConstraintIgnored
+ ReflexiveConstraintIgnored,
+ DuplicateForeignKeyConstraintIgnored,
}
private static readonly string _validationPrefix = DbLoggerCategory.Model.Validation.Name + ".";
@@ -230,5 +231,11 @@ private static EventId MakeScaffoldingId(Id id)
/// This event is in the category.
///
public static readonly EventId ReflexiveConstraintIgnored = MakeScaffoldingId(Id.ReflexiveConstraintIgnored);
+
+ ///
+ /// A duplicate foreign key constraint was skipped.
+ /// This event is in the category.
+ ///
+ public static readonly EventId DuplicateForeignKeyConstraintIgnored = MakeScaffoldingId(Id.DuplicateForeignKeyConstraintIgnored);
}
}
diff --git a/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs b/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs
index 67ab46e253f..3793ab6d8a4 100644
--- a/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs
+++ b/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs
@@ -511,6 +511,28 @@ public static void ReflexiveConstraintIgnored(
// No DiagnosticsSource events because these are purely design-time messages
}
+ ///
+ /// 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 static void DuplicateForeignKeyConstraintIgnored(
+ this IDiagnosticsLogger diagnostics,
+ string foreignKeyName,
+ string tableName,
+ string duplicateForeignKeyName)
+ {
+ var definition = SqlServerResources.DuplicateForeignKeyConstraintIgnored(diagnostics);
+
+ if (diagnostics.ShouldLog(definition))
+ {
+ definition.Log(diagnostics, foreignKeyName, tableName, duplicateForeignKeyName);
+ }
+
+ // No DiagnosticsSource events because these are purely design-time messages
+ }
+
///
/// Logs for the event.
///
diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs
index 80f340df44d..afd28b83c94 100644
--- a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs
+++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs
@@ -804,6 +804,31 @@ public static EventDefinition LogReflexiveConstraintIgnored(IDia
return (EventDefinition)definition;
}
+ ///
+ /// Skipping foreign key '{foreignKeyName}' on table '{tableName}' since it is a duplicate of '{duplicateForeignKeyName}'.
+ ///
+ public static EventDefinition DuplicateForeignKeyConstraintIgnored(IDiagnosticsLogger logger)
+ {
+ var definition = ((Diagnostics.Internal.SqlServerLoggingDefinitions)logger.Definitions).LogDuplicateForeignKeyConstraintIgnored;
+ if (definition == null)
+ {
+ definition = NonCapturingLazyInitializer.EnsureInitialized(
+ ref ((Diagnostics.Internal.SqlServerLoggingDefinitions)logger.Definitions).LogDuplicateForeignKeyConstraintIgnored,
+ logger,
+ static logger => new EventDefinition(
+ logger.Options,
+ SqlServerEventId.DuplicateForeignKeyConstraintIgnored,
+ LogLevel.Warning,
+ "SqlServerEventId.DuplicateForeignKeyConstraintIgnored",
+ level => LoggerMessage.Define(
+ level,
+ SqlServerEventId.DuplicateForeignKeyConstraintIgnored,
+ _resourceManager.GetString("DuplicateForeignKeyConstraintIgnored")!)));
+ }
+
+ return (EventDefinition)definition;
+ }
+
///
/// Savepoints are disabled because Multiple Active Result Sets (MARS) is enabled. If 'SaveChanges' fails, then the transaction cannot be automatically rolled back to a known clean state. Instead, the transaction should be rolled back by the application before retrying 'SaveChanges'. See https://go.microsoft.com/fwlink/?linkid=2149338 for more information. To identify the code which triggers this warning, call 'ConfigureWarnings(w => w.Throw(SqlServerEventId.SavepointsDisabledBecauseOfMARS))'.
///
diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx
index cc45f62a28b..1ed0c6f040c 100644
--- a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx
+++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx
@@ -252,6 +252,10 @@
Skipping foreign key '{foreignKeyName}' on table '{tableName}' since all of its columns reference themselves.
Debug SqlServerEventId.ReflexiveConstraintIgnored string string
+
+ Skipping foreign key '{foreignKeyName}' on table '{tableName}' since it is a duplicate of '{duplicateForeignKeyName}'.
+ Warning SqlServerEventId.DuplicateForeignKeyConstraintIgnored string string string
+
Savepoints are disabled because Multiple Active Result Sets (MARS) is enabled. If 'SaveChanges' fails, then the transaction cannot be automatically rolled back to a known clean state. Instead, the transaction should be rolled back by the application before retrying 'SaveChanges'. See https://go.microsoft.com/fwlink/?linkid=2149338 for more information. To identify the code which triggers this warning, call 'ConfigureWarnings(w => w.Throw(SqlServerEventId.SavepointsDisabledBecauseOfMARS))'.
Warning SqlServerEventId.SavepointsDisabledBecauseOfMARS
diff --git a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs
index 54f9ceab50c..3ba479ad344 100644
--- a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs
+++ b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs
@@ -1231,6 +1231,18 @@ FROM [sys].[foreign_keys] AS [f]
}
else
{
+ var duplicated = table.ForeignKeys
+ .FirstOrDefault(k => k.Columns.SequenceEqual(foreignKey.Columns)
+ && k.PrincipalTable.Equals(foreignKey.PrincipalTable));
+ if (duplicated != null)
+ {
+ _logger.DuplicateForeignKeyConstraintIgnored(
+ foreignKey.Name!,
+ DisplayName(table.Schema, table.Name!),
+ duplicated.Name!);
+ continue;
+ }
+
table.ForeignKeys.Add(foreignKey);
}
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs
index f25710fa5fe..f92326d4c64 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs
@@ -2370,6 +2370,45 @@ CONSTRAINT MYFK FOREIGN KEY (Id) REFERENCES PrincipalTable(Id)
DROP TABLE PrincipalTable;");
}
+ [ConditionalFact]
+ public void Skip_duplicate_foreign_key()
+ {
+ Test(
+ @"CREATE TABLE PrincipalTable (
+ Id int PRIMARY KEY,
+);
+
+CREATE TABLE OtherPrincipalTable (
+ Id int PRIMARY KEY,
+);
+
+CREATE TABLE DependentTable (
+ Id int PRIMARY KEY,
+ ForeignKeyId int,
+ CONSTRAINT MYFK1 FOREIGN KEY (ForeignKeyId) REFERENCES PrincipalTable(Id),
+ CONSTRAINT MYFK2 FOREIGN KEY (ForeignKeyId) REFERENCES PrincipalTable(Id),
+ CONSTRAINT MYFK3 FOREIGN KEY (ForeignKeyId) REFERENCES OtherPrincipalTable(Id),
+);",
+ Enumerable.Empty(),
+ Enumerable.Empty(),
+ dbModel =>
+ {
+ var (level, _, message, _, _) = Assert.Single(
+ Fixture.ListLoggerFactory.Log, t => t.Id == SqlServerEventId.DuplicateForeignKeyConstraintIgnored);
+ Assert.Equal(LogLevel.Warning, level);
+ Assert.Equal(
+ SqlServerResources.DuplicateForeignKeyConstraintIgnored(new TestLogger())
+ .GenerateMessage("MYFK2", "dbo.DependentTable", "MYFK1"), message);
+
+ var table = dbModel.Tables.Single(t => t.Name == "DependentTable");
+ Assert.Equal(2, table.ForeignKeys.Count);
+ },
+ @"
+DROP TABLE DependentTable;
+DROP TABLE PrincipalTable;
+DROP TABLE OtherPrincipalTable;");
+ }
+
#endregion
private void Test(