Skip to content

Allow excluding foreign key from migrations#37815

Open
roji wants to merge 1 commit intodotnet:mainfrom
roji:NoMoreFks
Open

Allow excluding foreign key from migrations#37815
roji wants to merge 1 commit intodotnet:mainfrom
roji:NoMoreFks

Conversation

@roji
Copy link
Member

@roji roji commented Feb 28, 2026

Closes #15854

@roji roji marked this pull request as ready for review February 28, 2026 17:18
@roji roji requested a review from a team as a code owner February 28, 2026 17:18
Copilot AI review requested due to automatic review settings February 28, 2026 17:18
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a relational model annotation and fluent API to allow foreign key constraints to exist in the EF model while being excluded from migrations (i.e., migrations won’t create/drop the DB constraint), closing #15854.

Changes:

  • Add ExcludeFromMigrations() on relationship builders and persist it via a new relational FK annotation.
  • Update migrations diffing to skip add/drop FK operations when a foreign key constraint is excluded.
  • Add design-time and functional test coverage (snapshot/codegen + provider SQL baselines) for the new behavior.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs Adds provider SQL assertion for excluded-FK migration (index only).
test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs Adds provider SQL assertion for excluded-FK migration (index only).
test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs Adds model differ tests validating operations when FK constraints are excluded.
test/EFCore.Relational.Tests/Extensions/RelationalMetadataExtensionsTest.cs Verifies get/set behavior for the new excluded-from-migrations FK metadata.
test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs Verifies fluent API surface and configuration-source behavior for FK exclusion.
test/EFCore.Relational.Tests/Design/AnnotationCodeGeneratorTest.cs Ensures snapshot codegen emits ExcludeFromMigrations fluent API for FKs.
test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs Adds cross-provider scenario ensuring DB FK constraints aren’t created.
test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs Updates “new annotations” guard lists for the new relational annotation name.
test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs Adds snapshot roundtrip test verifying FK exclusion is stored in snapshots.
src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs Skips FK add/drop operations when FK constraints are excluded from migrations.
src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs Introduces IsForeignKeyExcludedFromMigrations annotation name and registers it.
src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraint.cs Surfaces FK exclusion at the constraint level for migrations diffing.
src/EFCore.Relational/Metadata/IForeignKeyConstraint.cs Adds IsExcludedFromMigrations to the public constraint metadata interface.
src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs Removes the new annotation from runtime annotations.
src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs Adds metadata accessors for FK exclusion + configuration source helpers.
src/EFCore.Relational/Extensions/RelationalForeignKeyBuilderExtensions.cs Adds ExcludeFromMigrations fluent APIs for relationship builders.
src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs Removes the new annotation from runtime codegen output.
src/EFCore.Relational/Design/AnnotationCodeGenerator.cs Generates fluent API calls for FK exclusion in snapshots.

/// <summary>
/// Gets a value indicating whether the foreign key constraint is excluded from migrations.
/// </summary>
bool IsExcludedFromMigrations { get; }
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding IsExcludedFromMigrations as a new abstract member on the public IForeignKeyConstraint interface is a binary breaking change for any external implementations. To preserve compatibility, consider providing a default interface implementation (e.g. computing from MappedForeignKeys or returning false) or exposing this via an extension method instead of extending the interface contract.

Suggested change
bool IsExcludedFromMigrations { get; }
bool IsExcludedFromMigrations
=> false;

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AndriySvyryd let me know what you want to do here, AFAIK these interfaces aren't meant to be implemented by external users so this is probably not relevant?

Comment on lines +438 to +442
GenerateSimpleFluentApiCall(
annotations,
RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations,
nameof(RelationalForeignKeyBuilderExtensions.ExcludeFromMigrations),
methodCallCodeFragments);
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the new IsForeignKeyExcludedFromMigrations fluent API call generation, if the annotation ever ends up explicitly set to false (e.g. toggled from true back to false), the snapshot will likely emit .ExcludeFromMigrations(false) since RemoveAnnotationsHandledByConventions(IForeignKey, …) doesn’t special-case this annotation. Consider removing this annotation when its value is false (similar to how IsTableExcludedFromMigrations is handled) to avoid noisy/unstable snapshots.

Suggested change
GenerateSimpleFluentApiCall(
annotations,
RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations,
nameof(RelationalForeignKeyBuilderExtensions.ExcludeFromMigrations),
methodCallCodeFragments);
if (annotations.TryGetValue(
RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations,
out var excludeFromMigrationsAnnotation)
&& excludeFromMigrationsAnnotation.Value is bool isExcludedFromMigrations
&& !isExcludedFromMigrations)
{
annotations.Remove(RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations);
}
else
{
GenerateSimpleFluentApiCall(
annotations,
RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations,
nameof(RelationalForeignKeyBuilderExtensions.ExcludeFromMigrations),
methodCallCodeFragments);
}

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AndriySvyryd don't we actually prefer to bake in an explicit false when it's explicitly configured by the user?

/// <param name="referenceCollectionBuilder">The builder being used to configure the relationship.</param>
/// <param name="excluded">A value indicating whether the foreign key constraint is excluded from migrations.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static ReferenceCollectionBuilder ExcludeFromMigrations(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AndriySvyryd see suggestion to rename this to ExcludeForeignKeyFromMigrations, to make it clear what's being excluded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow FKs to exist in the model but avoid creating them in the database

3 participants