Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fb2dbcf

Browse files
deano-hunterDean Hunter
andauthored
Add fill factor to keys and unique constraints (#32900)
* Add FILLFACTOR for keys Fixes #32803 * Tidy up KeyWithOptions method and summary test --------- Co-authored-by: Dean Hunter <[email protected]>
1 parent a418545 commit fb2dbcf

File tree

15 files changed

+773
-8
lines changed

15 files changed

+773
-8
lines changed

src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,11 @@ protected virtual void Generate(
238238
.Append("ALTER TABLE ")
239239
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table, operation.Schema))
240240
.Append(" ADD ");
241+
241242
PrimaryKeyConstraint(operation, model, builder);
242243

244+
KeyWithOptions(operation, builder);
245+
243246
if (terminate)
244247
{
245248
builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
@@ -263,7 +266,11 @@ protected virtual void Generate(
263266
.Append("ALTER TABLE ")
264267
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table, operation.Schema))
265268
.Append(" ADD ");
269+
266270
UniqueConstraint(operation, model, builder);
271+
272+
KeyWithOptions(operation, builder);
273+
267274
builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
268275
EndStatement(builder);
269276
}
@@ -1709,6 +1716,16 @@ protected virtual void CheckConstraint(
17091716
.Append(")");
17101717
}
17111718

1719+
/// <summary>
1720+
/// Generates a SQL fragment for extra with options of a key from a
1721+
/// <see cref="AddPrimaryKeyOperation" /> or <see cref="AddUniqueConstraintOperation" />.
1722+
/// </summary>
1723+
/// <param name="operation">The operation.</param>
1724+
/// <param name="builder">The command builder to use to add the SQL fragment.</param>
1725+
protected virtual void KeyWithOptions(MigrationOperation operation, MigrationCommandListBuilder builder)
1726+
{
1727+
}
1728+
17121729
/// <summary>
17131730
/// Generates a SQL fragment for traits of an index from a <see cref="CreateIndexOperation" />,
17141731
/// <see cref="AddPrimaryKeyOperation" />, or <see cref="AddUniqueConstraintOperation" />.

src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ private static readonly MethodInfo KeyIsClusteredMethodInfo
9191
= typeof(SqlServerKeyBuilderExtensions).GetRuntimeMethod(
9292
nameof(SqlServerKeyBuilderExtensions.IsClustered), [typeof(KeyBuilder), typeof(bool)])!;
9393

94+
private static readonly MethodInfo KeyHasFillFactorMethodInfo
95+
= typeof(SqlServerKeyBuilderExtensions).GetRuntimeMethod(
96+
nameof(SqlServerKeyBuilderExtensions.HasFillFactor), [typeof(KeyBuilder), typeof(int)])!;
97+
9498
private static readonly MethodInfo TableIsTemporalMethodInfo
9599
= typeof(SqlServerTableBuilderExtensions).GetRuntimeMethod(
96100
nameof(SqlServerTableBuilderExtensions.IsTemporal), [typeof(TableBuilder), typeof(bool)])!;
@@ -328,11 +332,16 @@ protected override bool IsHandledByConvention(IProperty property, IAnnotation an
328332
/// doing so can result in application failures when updating to a new Entity Framework Core release.
329333
/// </summary>
330334
protected override MethodCallCodeFragment? GenerateFluentApi(IKey key, IAnnotation annotation)
331-
=> annotation.Name == SqlServerAnnotationNames.Clustered
332-
? (bool)annotation.Value! == false
335+
=> annotation.Name switch
336+
{
337+
SqlServerAnnotationNames.Clustered => (bool)annotation.Value! == false
333338
? new MethodCallCodeFragment(KeyIsClusteredMethodInfo, false)
334-
: new MethodCallCodeFragment(KeyIsClusteredMethodInfo)
335-
: null;
339+
: new MethodCallCodeFragment(KeyIsClusteredMethodInfo),
340+
341+
SqlServerAnnotationNames.FillFactor => new MethodCallCodeFragment(KeyHasFillFactorMethodInfo, annotation.Value),
342+
343+
_ => null
344+
};
336345

337346
/// <summary>
338347
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to

src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public override void Generate(IKey key, CSharpRuntimeAnnotationCodeGeneratorPara
132132
{
133133
var annotations = parameters.Annotations;
134134
annotations.Remove(SqlServerAnnotationNames.Clustered);
135+
annotations.Remove(SqlServerAnnotationNames.FillFactor);
135136
}
136137

137138
base.Generate(key, parameters);
@@ -144,6 +145,7 @@ public override void Generate(IUniqueConstraint uniqueConstraint, CSharpRuntimeA
144145
{
145146
var annotations = parameters.Annotations;
146147
annotations.Remove(SqlServerAnnotationNames.Clustered);
148+
annotations.Remove(SqlServerAnnotationNames.FillFactor);
147149
}
148150

149151
base.Generate(uniqueConstraint, parameters);

src/EFCore.SqlServer/Extensions/SqlServerKeyBuilderExtensions.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,86 @@ public static bool CanSetIsClustered(
9696
bool? clustered,
9797
bool fromDataAnnotation = false)
9898
=> keyBuilder.CanSetAnnotation(SqlServerAnnotationNames.Clustered, clustered, fromDataAnnotation);
99+
100+
/// <summary>
101+
/// Configures whether the key is created with fill factor option when targeting SQL Server.
102+
/// </summary>
103+
/// <remarks>
104+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
105+
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
106+
/// for more information and examples.
107+
/// </remarks>
108+
/// <param name="keyBuilder">The builder for the key being configured.</param>
109+
/// <param name="fillFactor">A value indicating whether the key is created with fill factor option.</param>
110+
/// <returns>A builder to further configure the key.</returns>
111+
public static KeyBuilder HasFillFactor(this KeyBuilder keyBuilder, int fillFactor)
112+
{
113+
keyBuilder.Metadata.SetFillFactor(fillFactor);
114+
115+
return keyBuilder;
116+
}
117+
118+
/// <summary>
119+
/// Configures whether the key is created with fill factor option when targeting SQL Server.
120+
/// </summary>
121+
/// <remarks>
122+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
123+
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
124+
/// for more information and examples.
125+
/// </remarks>
126+
/// <param name="keyBuilder">The builder for the key being configured.</param>
127+
/// <param name="fillFactor">A value indicating whether the key is created with fill factor option.</param>
128+
/// <returns>A builder to further configure the key.</returns>
129+
public static KeyBuilder<TEntity> HasFillFactor<TEntity>(
130+
this KeyBuilder<TEntity> keyBuilder,
131+
int fillFactor)
132+
=> (KeyBuilder<TEntity>)HasFillFactor((KeyBuilder)keyBuilder, fillFactor);
133+
134+
/// <summary>
135+
/// Configures whether the key is created with fill factor option when targeting SQL Server.
136+
/// </summary>
137+
/// <remarks>
138+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
139+
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
140+
/// for more information and examples.
141+
/// </remarks>
142+
/// <param name="keyBuilder">The builder for the key being configured.</param>
143+
/// <param name="fillFactor">A value indicating whether the key is created with fill factor option.</param>
144+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
145+
/// <returns>
146+
/// The same builder instance if the configuration was applied,
147+
/// <see langword="null" /> otherwise.
148+
/// </returns>
149+
public static IConventionKeyBuilder? HasFillFactor(
150+
this IConventionKeyBuilder keyBuilder,
151+
int? fillFactor,
152+
bool fromDataAnnotation = false)
153+
{
154+
if (keyBuilder.CanSetFillFactor(fillFactor, fromDataAnnotation))
155+
{
156+
keyBuilder.Metadata.SetFillFactor(fillFactor, fromDataAnnotation);
157+
158+
return keyBuilder;
159+
}
160+
161+
return null;
162+
}
163+
164+
/// <summary>
165+
/// Returns a value indicating whether the key can be configured with fill factor option when targeting SQL Server.
166+
/// </summary>
167+
/// <remarks>
168+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
169+
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
170+
/// for more information and examples.
171+
/// </remarks>
172+
/// <param name="keyBuilder">The builder for the key being configured.</param>
173+
/// <param name="fillFactor">A value indicating whether the key is created with fill factor option.</param>
174+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
175+
/// <returns><see langword="true" /> if the key can be configured with fill factor option when targeting SQL Server.</returns>
176+
public static bool CanSetFillFactor(
177+
this IConventionKeyBuilder keyBuilder,
178+
int? fillFactor,
179+
bool fromDataAnnotation = false)
180+
=> keyBuilder.CanSetAnnotation(SqlServerAnnotationNames.FillFactor, fillFactor, fromDataAnnotation);
99181
}

src/EFCore.SqlServer/Extensions/SqlServerKeyExtensions.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,85 @@ public static void SetIsClustered(this IMutableKey key, bool? clustered)
8282
/// <returns>The <see cref="ConfigurationSource" /> for whether the key is clustered.</returns>
8383
public static ConfigurationSource? GetIsClusteredConfigurationSource(this IConventionKey key)
8484
=> key.FindAnnotation(SqlServerAnnotationNames.Clustered)?.GetConfigurationSource();
85+
86+
/// <summary>
87+
/// Returns the fill factor that the key uses.
88+
/// </summary>
89+
/// <param name="key">The key.</param>
90+
/// <returns>The fill factor that the key uses</returns>
91+
public static int? GetFillFactor(this IReadOnlyKey key)
92+
=> (key is RuntimeKey)
93+
? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData)
94+
: (int?)key[SqlServerAnnotationNames.FillFactor];
95+
96+
/// <summary>
97+
/// Returns the fill factor that the key uses.
98+
/// </summary>
99+
/// <param name="key">The key.</param>
100+
/// <param name="storeObject">The identifier of the store object.</param>
101+
/// <returns>The fill factor that the key uses</returns>
102+
public static int? GetFillFactor(this IReadOnlyKey key, in StoreObjectIdentifier storeObject)
103+
{
104+
if (key is RuntimeKey)
105+
{
106+
throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData);
107+
}
108+
109+
var annotation = key.FindAnnotation(SqlServerAnnotationNames.FillFactor);
110+
if (annotation != null)
111+
{
112+
return (int?)annotation.Value;
113+
}
114+
115+
var sharedTableRootKey = key.FindSharedObjectRootKey(storeObject);
116+
return sharedTableRootKey?.GetFillFactor(storeObject);
117+
}
118+
119+
/// <summary>
120+
/// Sets a value for fill factor the key uses.
121+
/// </summary>
122+
/// <param name="key">The key.</param>
123+
/// <param name="fillFactor">The value to set.</param>
124+
public static void SetFillFactor(this IMutableKey key, int? fillFactor)
125+
{
126+
if (fillFactor is <= 0 or > 100)
127+
{
128+
throw new ArgumentOutOfRangeException(nameof(fillFactor));
129+
}
130+
131+
key.SetAnnotation(
132+
SqlServerAnnotationNames.FillFactor,
133+
fillFactor);
134+
}
135+
136+
/// <summary>
137+
/// Sets a value for fill factor the key uses.
138+
/// </summary>
139+
/// <param name="key">The key.</param>
140+
/// <param name="fillFactor">The value to set.</param>
141+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
142+
/// <returns>The configured value.</returns>
143+
public static int? SetFillFactor(
144+
this IConventionKey key,
145+
int? fillFactor,
146+
bool fromDataAnnotation = false)
147+
{
148+
if (fillFactor is <= 0 or > 100)
149+
{
150+
throw new ArgumentOutOfRangeException(nameof(fillFactor));
151+
}
152+
153+
return (int?)key.SetAnnotation(
154+
SqlServerAnnotationNames.FillFactor,
155+
fillFactor,
156+
fromDataAnnotation)?.Value;
157+
}
158+
159+
/// <summary>
160+
/// Returns the <see cref="ConfigurationSource" /> for whether the key uses the fill factor.
161+
/// </summary>
162+
/// <param name="key">The key.</param>
163+
/// <returns>The <see cref="ConfigurationSource" /> for whether the key uses the fill factor.</returns>
164+
public static ConfigurationSource? GetFillFactorConfigurationSource(this IConventionKey key)
165+
=> key.FindAnnotation(SqlServerAnnotationNames.FillFactor)?.GetConfigurationSource();
85166
}

src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ protected override void ProcessKeyAnnotations(
116116
if (!runtime)
117117
{
118118
annotations.Remove(SqlServerAnnotationNames.Clustered);
119+
annotations.Remove(SqlServerAnnotationNames.FillFactor);
119120
}
120121
}
121122

src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ public override IEnumerable<IAnnotation> For(IUniqueConstraint constraint, bool
158158
{
159159
yield return new Annotation(SqlServerAnnotationNames.Clustered, isClustered);
160160
}
161+
162+
if (key.GetFillFactor() is int fillFactor)
163+
{
164+
yield return new Annotation(SqlServerAnnotationNames.FillFactor, fillFactor);
165+
}
161166
}
162167

163168
/// <summary>

src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,6 +1775,30 @@ protected virtual void Transfer(
17751775
}
17761776
}
17771777

1778+
/// <summary>
1779+
/// Generates a SQL fragment for extra with options of a key from a
1780+
/// <see cref="AddPrimaryKeyOperation" />, or <see cref="AddUniqueConstraintOperation" />.
1781+
/// </summary>
1782+
/// <param name="operation">The operation.</param>
1783+
/// <param name="builder">The command builder to use to add the SQL fragment.</param>
1784+
protected override void KeyWithOptions(MigrationOperation operation, MigrationCommandListBuilder builder)
1785+
{
1786+
var options = new List<string>();
1787+
1788+
if (operation[SqlServerAnnotationNames.FillFactor] is int fillFactor)
1789+
{
1790+
options.Add("FILLFACTOR = " + fillFactor);
1791+
}
1792+
1793+
if (options.Count > 0)
1794+
{
1795+
builder
1796+
.Append(" WITH (")
1797+
.Append(string.Join(", ", options))
1798+
.Append(")");
1799+
}
1800+
}
1801+
17781802
/// <summary>
17791803
/// Generates a SQL fragment for traits of an index from a <see cref="CreateIndexOperation" />,
17801804
/// <see cref="AddPrimaryKeyOperation" />, or <see cref="AddUniqueConstraintOperation" />.

src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,7 +1087,8 @@ FROM [sys].[indexes] i
10871087
.GroupBy(
10881088
ddr =>
10891089
(Name: ddr.GetFieldValue<string>("index_name"),
1090-
TypeDesc: ddr.GetValueOrDefault<string>("type_desc")))
1090+
TypeDesc: ddr.GetValueOrDefault<string>("type_desc"),
1091+
FillFactor: ddr.GetValueOrDefault<byte>("fill_factor")))
10911092
.ToArray();
10921093

10931094
Check.DebugAssert(primaryKeyGroups.Length is 0 or 1, "Multiple primary keys found");
@@ -1106,7 +1107,8 @@ FROM [sys].[indexes] i
11061107
.GroupBy(
11071108
ddr =>
11081109
(Name: ddr.GetValueOrDefault<string>("index_name"),
1109-
TypeDesc: ddr.GetValueOrDefault<string>("type_desc")))
1110+
TypeDesc: ddr.GetValueOrDefault<string>("type_desc"),
1111+
FillFactor: ddr.GetValueOrDefault<byte>("fill_factor")))
11101112
.ToArray();
11111113

11121114
foreach (var uniqueConstraintGroup in uniqueConstraintGroups)
@@ -1142,7 +1144,7 @@ FROM [sys].[indexes] i
11421144
}
11431145

11441146
bool TryGetPrimaryKey(
1145-
IGrouping<(string Name, string? TypeDesc), DbDataRecord> primaryKeyGroup,
1147+
IGrouping<(string Name, string? TypeDesc, byte FillFactor), DbDataRecord> primaryKeyGroup,
11461148
[NotNullWhen(true)] out DatabasePrimaryKey? primaryKey)
11471149
{
11481150
primaryKey = new DatabasePrimaryKey { Table = table, Name = primaryKeyGroup.Key.Name };
@@ -1152,6 +1154,11 @@ bool TryGetPrimaryKey(
11521154
primaryKey[SqlServerAnnotationNames.Clustered] = false;
11531155
}
11541156

1157+
if (primaryKeyGroup.Key.FillFactor is > 0 and <= 100)
1158+
{
1159+
primaryKey[SqlServerAnnotationNames.FillFactor] = (int)primaryKeyGroup.Key.FillFactor;
1160+
}
1161+
11551162
foreach (var dataRecord in primaryKeyGroup)
11561163
{
11571164
var columnName = dataRecord.GetValueOrDefault<string>("column_name");
@@ -1171,7 +1178,7 @@ bool TryGetPrimaryKey(
11711178
}
11721179

11731180
bool TryGetUniqueConstraint(
1174-
IGrouping<(string? Name, string? TypeDesc), DbDataRecord> uniqueConstraintGroup,
1181+
IGrouping<(string? Name, string? TypeDesc, byte FillFactor), DbDataRecord> uniqueConstraintGroup,
11751182
[NotNullWhen(true)] out DatabaseUniqueConstraint? uniqueConstraint)
11761183
{
11771184
uniqueConstraint = new DatabaseUniqueConstraint { Table = table, Name = uniqueConstraintGroup.Key.Name };
@@ -1181,6 +1188,11 @@ bool TryGetUniqueConstraint(
11811188
uniqueConstraint[SqlServerAnnotationNames.Clustered] = true;
11821189
}
11831190

1191+
if (uniqueConstraintGroup.Key.FillFactor is > 0 and <= 100)
1192+
{
1193+
uniqueConstraint[SqlServerAnnotationNames.FillFactor] = (int)uniqueConstraintGroup.Key.FillFactor;
1194+
}
1195+
11841196
foreach (var dataRecord in uniqueConstraintGroup)
11851197
{
11861198
var columnName = dataRecord.GetValueOrDefault<string>("column_name");

0 commit comments

Comments
 (0)