Skip to content

Commit e0da9f7

Browse files
committed
Allow excluding foreign key from migrations
Closes #15854
1 parent fbf051c commit e0da9f7

18 files changed

Lines changed: 571 additions & 4 deletions

File tree

src/EFCore.Relational/Design/AnnotationCodeGenerator.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,12 @@ public virtual IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
435435
nameof(RelationalForeignKeyBuilderExtensions.HasConstraintName),
436436
methodCallCodeFragments);
437437

438+
GenerateSimpleFluentApiCall(
439+
annotations,
440+
RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations,
441+
nameof(RelationalForeignKeyBuilderExtensions.ExcludeFromMigrations),
442+
methodCallCodeFragments);
443+
438444
methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(foreignKey, annotations, GenerateFluentApi));
439445

440446
return methodCallCodeFragments;
@@ -1051,6 +1057,7 @@ private static void GenerateSimpleFluentApiCall(
10511057
if (annotations.TryGetValue(annotationName, out var annotation))
10521058
{
10531059
annotations.Remove(annotationName);
1060+
10541061
if (annotation.Value is { } annotationValue)
10551062
{
10561063
methodCallCodeFragments.Add(

src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,6 +2167,7 @@ public override void Generate(IForeignKey foreignKey, CSharpRuntimeAnnotationCod
21672167
if (parameters.IsRuntime)
21682168
{
21692169
parameters.Annotations.Remove(RelationalAnnotationNames.ForeignKeyMappings);
2170+
parameters.Annotations.Remove(RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations);
21702171
}
21712172

21722173
base.Generate(foreignKey, parameters);

src/EFCore.Relational/Extensions/RelationalForeignKeyBuilderExtensions.cs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,166 @@ public static bool CanSetConstraintName(
173173
string? name,
174174
bool fromDataAnnotation = false)
175175
=> relationship.CanSetAnnotation(RelationalAnnotationNames.Name, name, fromDataAnnotation);
176+
177+
/// <summary>
178+
/// Configures whether the foreign key constraint is excluded from migrations
179+
/// when targeting a relational database.
180+
/// </summary>
181+
/// <remarks>
182+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
183+
/// </remarks>
184+
/// <param name="referenceCollectionBuilder">The builder being used to configure the relationship.</param>
185+
/// <param name="excluded">A value indicating whether the foreign key constraint is excluded from migrations.</param>
186+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
187+
public static ReferenceCollectionBuilder ExcludeFromMigrations(
188+
this ReferenceCollectionBuilder referenceCollectionBuilder,
189+
bool excluded = true)
190+
{
191+
referenceCollectionBuilder.Metadata.SetIsExcludedFromMigrations(excluded);
192+
193+
return referenceCollectionBuilder;
194+
}
195+
196+
/// <summary>
197+
/// Configures whether the foreign key constraint is excluded from migrations
198+
/// when targeting a relational database.
199+
/// </summary>
200+
/// <remarks>
201+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
202+
/// </remarks>
203+
/// <param name="referenceCollectionBuilder">The builder being used to configure the relationship.</param>
204+
/// <param name="excluded">A value indicating whether the foreign key constraint is excluded from migrations.</param>
205+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
206+
/// <typeparam name="TEntity">The principal entity type in this relationship.</typeparam>
207+
/// <typeparam name="TRelatedEntity">The dependent entity type in this relationship.</typeparam>
208+
public static ReferenceCollectionBuilder<TEntity, TRelatedEntity> ExcludeFromMigrations<TEntity, TRelatedEntity>(
209+
this ReferenceCollectionBuilder<TEntity, TRelatedEntity> referenceCollectionBuilder,
210+
bool excluded = true)
211+
where TEntity : class
212+
where TRelatedEntity : class
213+
=> (ReferenceCollectionBuilder<TEntity, TRelatedEntity>)ExcludeFromMigrations(
214+
(ReferenceCollectionBuilder)referenceCollectionBuilder, excluded);
215+
216+
/// <summary>
217+
/// Configures whether the foreign key constraint is excluded from migrations
218+
/// when targeting a relational database.
219+
/// </summary>
220+
/// <remarks>
221+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
222+
/// </remarks>
223+
/// <param name="referenceReferenceBuilder">The builder being used to configure the relationship.</param>
224+
/// <param name="excluded">A value indicating whether the foreign key constraint is excluded from migrations.</param>
225+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
226+
public static ReferenceReferenceBuilder ExcludeFromMigrations(
227+
this ReferenceReferenceBuilder referenceReferenceBuilder,
228+
bool excluded = true)
229+
{
230+
referenceReferenceBuilder.Metadata.SetIsExcludedFromMigrations(excluded);
231+
232+
return referenceReferenceBuilder;
233+
}
234+
235+
/// <summary>
236+
/// Configures whether the foreign key constraint is excluded from migrations
237+
/// when targeting a relational database.
238+
/// </summary>
239+
/// <remarks>
240+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
241+
/// </remarks>
242+
/// <param name="referenceReferenceBuilder">The builder being used to configure the relationship.</param>
243+
/// <param name="excluded">A value indicating whether the foreign key constraint is excluded from migrations.</param>
244+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
245+
/// <typeparam name="TEntity">The entity type on one end of the relationship.</typeparam>
246+
/// <typeparam name="TRelatedEntity">The entity type on the other end of the relationship.</typeparam>
247+
public static ReferenceReferenceBuilder<TEntity, TRelatedEntity> ExcludeFromMigrations<TEntity, TRelatedEntity>(
248+
this ReferenceReferenceBuilder<TEntity, TRelatedEntity> referenceReferenceBuilder,
249+
bool excluded = true)
250+
where TEntity : class
251+
where TRelatedEntity : class
252+
=> (ReferenceReferenceBuilder<TEntity, TRelatedEntity>)ExcludeFromMigrations(
253+
(ReferenceReferenceBuilder)referenceReferenceBuilder, excluded);
254+
255+
/// <summary>
256+
/// Configures whether the foreign key constraint is excluded from migrations
257+
/// when targeting a relational database.
258+
/// </summary>
259+
/// <remarks>
260+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
261+
/// </remarks>
262+
/// <param name="ownershipBuilder">The builder being used to configure the relationship.</param>
263+
/// <param name="excluded">A value indicating whether the foreign key constraint is excluded from migrations.</param>
264+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
265+
public static OwnershipBuilder ExcludeFromMigrations(
266+
this OwnershipBuilder ownershipBuilder,
267+
bool excluded = true)
268+
{
269+
ownershipBuilder.Metadata.SetIsExcludedFromMigrations(excluded);
270+
271+
return ownershipBuilder;
272+
}
273+
274+
/// <summary>
275+
/// Configures whether the foreign key constraint is excluded from migrations
276+
/// when targeting a relational database.
277+
/// </summary>
278+
/// <remarks>
279+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
280+
/// </remarks>
281+
/// <param name="ownershipBuilder">The builder being used to configure the relationship.</param>
282+
/// <param name="excluded">A value indicating whether the foreign key constraint is excluded from migrations.</param>
283+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
284+
/// <typeparam name="TEntity">The entity type on one end of the relationship.</typeparam>
285+
/// <typeparam name="TDependentEntity">The entity type on the other end of the relationship.</typeparam>
286+
public static OwnershipBuilder<TEntity, TDependentEntity> ExcludeFromMigrations<TEntity, TDependentEntity>(
287+
this OwnershipBuilder<TEntity, TDependentEntity> ownershipBuilder,
288+
bool excluded = true)
289+
where TEntity : class
290+
where TDependentEntity : class
291+
=> (OwnershipBuilder<TEntity, TDependentEntity>)ExcludeFromMigrations(
292+
(OwnershipBuilder)ownershipBuilder, excluded);
293+
294+
/// <summary>
295+
/// Configures whether the foreign key constraint is excluded from migrations
296+
/// when targeting a relational database.
297+
/// </summary>
298+
/// <remarks>
299+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
300+
/// </remarks>
301+
/// <param name="relationship">The builder being used to configure the relationship.</param>
302+
/// <param name="excluded">A value indicating whether the foreign key constraint is excluded from migrations.</param>
303+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
304+
/// <returns>
305+
/// The same builder instance if the configuration was applied,
306+
/// <see langword="null" /> otherwise.
307+
/// </returns>
308+
public static IConventionForeignKeyBuilder? ExcludeFromMigrations(
309+
this IConventionForeignKeyBuilder relationship,
310+
bool? excluded,
311+
bool fromDataAnnotation = false)
312+
{
313+
if (!relationship.CanSetExcludeFromMigrations(excluded, fromDataAnnotation))
314+
{
315+
return null;
316+
}
317+
318+
relationship.Metadata.SetIsExcludedFromMigrations(excluded, fromDataAnnotation);
319+
return relationship;
320+
}
321+
322+
/// <summary>
323+
/// Returns a value indicating whether the foreign key constraint exclusion from migrations can be set
324+
/// from the current configuration source.
325+
/// </summary>
326+
/// <remarks>
327+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
328+
/// </remarks>
329+
/// <param name="relationship">The builder being used to configure the relationship.</param>
330+
/// <param name="excluded">A value indicating whether the foreign key constraint is excluded from migrations.</param>
331+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
332+
/// <returns><see langword="true" /> if the configuration can be applied.</returns>
333+
public static bool CanSetExcludeFromMigrations(
334+
this IConventionForeignKeyBuilder relationship,
335+
bool? excluded,
336+
bool fromDataAnnotation = false)
337+
=> relationship.CanSetAnnotation(RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations, excluded, fromDataAnnotation);
176338
}

src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,45 @@ static bool IsMapped(IReadOnlyForeignKey foreignKey, StoreObjectIdentifier store
288288
this IForeignKey foreignKey,
289289
in StoreObjectIdentifier storeObject)
290290
=> (IForeignKey?)((IReadOnlyForeignKey)foreignKey).FindSharedObjectRootForeignKey(storeObject);
291+
292+
/// <summary>
293+
/// Returns a value indicating whether the foreign key constraint is excluded from migrations.
294+
/// </summary>
295+
/// <param name="foreignKey">The foreign key.</param>
296+
/// <returns><see langword="true" /> if the foreign key constraint is excluded from migrations.</returns>
297+
public static bool IsExcludedFromMigrations(this IReadOnlyForeignKey foreignKey)
298+
=> (bool?)foreignKey[RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations] ?? false;
299+
300+
/// <summary>
301+
/// Sets a value indicating whether the foreign key constraint is excluded from migrations.
302+
/// </summary>
303+
/// <param name="foreignKey">The foreign key.</param>
304+
/// <param name="excluded">The value to set.</param>
305+
public static void SetIsExcludedFromMigrations(this IMutableForeignKey foreignKey, bool? excluded)
306+
=> foreignKey.SetOrRemoveAnnotation(RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations, excluded);
307+
308+
/// <summary>
309+
/// Sets a value indicating whether the foreign key constraint is excluded from migrations.
310+
/// </summary>
311+
/// <param name="foreignKey">The foreign key.</param>
312+
/// <param name="excluded">The value to set.</param>
313+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
314+
/// <returns>The configured value.</returns>
315+
public static bool? SetIsExcludedFromMigrations(
316+
this IConventionForeignKey foreignKey,
317+
bool? excluded,
318+
bool fromDataAnnotation = false)
319+
=> (bool?)foreignKey.SetOrRemoveAnnotation(
320+
RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations,
321+
excluded,
322+
fromDataAnnotation)?.Value;
323+
324+
/// <summary>
325+
/// Gets the <see cref="ConfigurationSource" /> for the foreign key exclusion from migrations.
326+
/// </summary>
327+
/// <param name="foreignKey">The foreign key.</param>
328+
/// <returns>The <see cref="ConfigurationSource" /> for the foreign key exclusion from migrations.</returns>
329+
public static ConfigurationSource? GetIsExcludedFromMigrationsConfigurationSource(this IConventionForeignKey foreignKey)
330+
=> foreignKey.FindAnnotation(RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations)
331+
?.GetConfigurationSource();
291332
}

src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ protected override void ProcessForeignKeyAnnotations(
505505
if (runtime)
506506
{
507507
annotations.Remove(RelationalAnnotationNames.ForeignKeyMappings);
508+
annotations.Remove(RelationalAnnotationNames.IsForeignKeyExcludedFromMigrations);
508509
}
509510
}
510511

src/EFCore.Relational/Metadata/IForeignKeyConstraint.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ IReadOnlyList<IColumn> PrincipalColumns
5555
/// </summary>
5656
ReferentialAction OnDeleteAction { get; }
5757

58+
/// <summary>
59+
/// Gets a value indicating whether the foreign key constraint is excluded from migrations.
60+
/// </summary>
61+
bool IsExcludedFromMigrations { get; }
62+
5863
/// <summary>
5964
/// <para>
6065
/// Creates a human-readable representation of the given metadata.

src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraint.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ public override bool IsReadOnly
102102
/// <inheritdoc />
103103
public virtual ReferentialAction OnDeleteAction { get; set; }
104104

105+
/// <summary>
106+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
107+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
108+
/// any release. You should only use it directly in your code with extreme caution and knowing that
109+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
110+
/// </summary>
111+
public virtual bool IsExcludedFromMigrations
112+
=> ((IReadOnlyForeignKey)MappedForeignKeys.First()).IsExcludedFromMigrations();
113+
105114
/// <summary>
106115
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
107116
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ public static class RelationalAnnotationNames
174174
/// </summary>
175175
public const string IsTableExcludedFromMigrations = Prefix + "IsTableExcludedFromMigrations";
176176

177+
/// <summary>
178+
/// The name for the annotation determining whether the foreign key constraint is excluded from migrations.
179+
/// </summary>
180+
public const string IsForeignKeyExcludedFromMigrations = Prefix + "IsForeignKeyExcludedFromMigrations";
181+
177182
/// <summary>
178183
/// The name for the annotation determining the mapping strategy for inherited properties.
179184
/// </summary>
@@ -396,6 +401,7 @@ public static class RelationalAnnotationNames
396401
IsFixedLength,
397402
ViewDefinitionSql,
398403
IsTableExcludedFromMigrations,
404+
IsForeignKeyExcludedFromMigrations,
399405
MappingStrategy,
400406
RelationalModel,
401407
RelationalModelFactory,

src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,7 +1429,8 @@ protected virtual IEnumerable<MigrationOperation> Diff(
14291429
protected virtual IEnumerable<MigrationOperation> Add(IForeignKeyConstraint target, DiffContext diffContext)
14301430
{
14311431
var targetTable = target.Table;
1432-
if (targetTable.IsExcludedFromMigrations)
1432+
if (targetTable.IsExcludedFromMigrations
1433+
|| target.IsExcludedFromMigrations)
14331434
{
14341435
yield break;
14351436
}
@@ -1456,7 +1457,8 @@ protected virtual IEnumerable<MigrationOperation> Add(IForeignKeyConstraint targ
14561457
protected virtual IEnumerable<MigrationOperation> Remove(IForeignKeyConstraint source, DiffContext diffContext)
14571458
{
14581459
var sourceTable = source.Table;
1459-
if (sourceTable.IsExcludedFromMigrations)
1460+
if (sourceTable.IsExcludedFromMigrations
1461+
|| source.IsExcludedFromMigrations)
14601462
{
14611463
yield break;
14621464
}

0 commit comments

Comments
 (0)