Skip to content

Commit 7f62f0c

Browse files
committed
Allow configuring SQL Server temporal table period columns as not hidden
Adds TemporalTableBuilder.PeriodColumnsHidden(bool hidden = true) to opt out of the HIDDEN flag on period start/end columns. Default remains HIDDEN to preserve backward compatibility. Fixes #36608
1 parent f11c5e9 commit 7f62f0c

13 files changed

Lines changed: 322 additions & 7 deletions

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ private static readonly MethodInfo TemporalPropertyHasColumnNameMethodInfo
148148
= typeof(TemporalPeriodPropertyBuilder).GetRuntimeMethod(
149149
nameof(TemporalPeriodPropertyBuilder.HasColumnName), [typeof(string)])!;
150150

151+
private static readonly MethodInfo TemporalTablePeriodColumnsHiddenMethodInfo
152+
= typeof(TemporalTableBuilder).GetRuntimeMethod(
153+
nameof(TemporalTableBuilder.PeriodColumnsHidden), [typeof(bool)])!;
154+
151155
private static readonly MethodInfo ModelHasFullTextCatalogMethodInfo
152156
= typeof(SqlServerModelBuilderExtensions).GetRuntimeMethod(
153157
nameof(SqlServerModelBuilderExtensions.HasFullTextCatalog), [typeof(ModelBuilder), typeof(string)])!;
@@ -385,6 +389,14 @@ public override IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
385389
.Chain(new MethodCallCodeFragment(TemporalPropertyHasColumnNameMethodInfo, periodEndColumnName))
386390
: new MethodCallCodeFragment(TemporalTableHasPeriodEndMethodInfo, periodEndPropertyName));
387391

392+
// ttb => ttb.PeriodColumnsHidden(false)
393+
// Only emit when explicitly set to false; the default (true) matches legacy behavior.
394+
if (annotations.TryGetValue(SqlServerAnnotationNames.TemporalPeriodColumnsHidden, out var periodColumnsHiddenAnnotation)
395+
&& periodColumnsHiddenAnnotation.Value as bool? == false)
396+
{
397+
temporalTableBuilderCalls.Add(new MethodCallCodeFragment(TemporalTablePeriodColumnsHiddenMethodInfo, false));
398+
}
399+
388400
// ToTable(tb => tb.IsTemporal(ttb => { ... }))
389401
var toTemporalTableCall = new MethodCallCodeFragment(
390402
EntityTypeToTableMethodInfo,
@@ -403,6 +415,7 @@ public override IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
403415
annotations.Remove(SqlServerAnnotationNames.TemporalHistoryTableSchema);
404416
annotations.Remove(SqlServerAnnotationNames.TemporalPeriodStartPropertyName);
405417
annotations.Remove(SqlServerAnnotationNames.TemporalPeriodEndPropertyName);
418+
annotations.Remove(SqlServerAnnotationNames.TemporalPeriodColumnsHidden);
406419
}
407420

408421
return fragments;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public override void Generate(IColumn column, CSharpRuntimeAnnotationCodeGenerat
8888
annotations.Remove(SqlServerAnnotationNames.Sparse);
8989
annotations.Remove(SqlServerAnnotationNames.TemporalIsPeriodStartColumn);
9090
annotations.Remove(SqlServerAnnotationNames.TemporalIsPeriodEndColumn);
91+
annotations.Remove(SqlServerAnnotationNames.TemporalPeriodColumnsHidden);
9192
}
9293

9394
base.Generate(column, parameters);
@@ -171,6 +172,7 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod
171172
annotations.Remove(SqlServerAnnotationNames.TemporalHistoryTableSchema);
172173
annotations.Remove(SqlServerAnnotationNames.TemporalPeriodEndPropertyName);
173174
annotations.Remove(SqlServerAnnotationNames.TemporalPeriodStartPropertyName);
175+
annotations.Remove(SqlServerAnnotationNames.TemporalPeriodColumnsHidden);
174176
}
175177

176178
base.Generate(entityType, parameters);
@@ -187,6 +189,7 @@ public override void Generate(ITable table, CSharpRuntimeAnnotationCodeGenerator
187189
annotations.Remove(SqlServerAnnotationNames.TemporalHistoryTableSchema);
188190
annotations.Remove(SqlServerAnnotationNames.TemporalPeriodEndColumnName);
189191
annotations.Remove(SqlServerAnnotationNames.TemporalPeriodStartColumnName);
192+
annotations.Remove(SqlServerAnnotationNames.TemporalPeriodColumnsHidden);
190193
}
191194

192195
base.Generate(table, parameters);

src/EFCore.SqlServer/EFCore.SqlServer.baseline.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@
238238
{
239239
"Member": "override string? ToString();"
240240
},
241+
{
242+
"Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationTemporalTableBuilder PeriodColumnsHidden(bool hidden = true);"
243+
},
241244
{
242245
"Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationTemporalTableBuilder UseHistoryTable(string name);"
243246
},
@@ -255,6 +258,9 @@
255258
{
256259
"Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationTemporalPeriodPropertyBuilder HasPeriodStart(System.Linq.Expressions.Expression<System.Func<TDependentEntity, System.DateTime>> propertyExpression);"
257260
},
261+
{
262+
"Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationTemporalTableBuilder<TOwnerEntity, TDependentEntity> PeriodColumnsHidden(bool hidden = true);"
263+
},
258264
{
259265
"Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationTemporalTableBuilder<TOwnerEntity, TDependentEntity> UseHistoryTable(string name);"
260266
},
@@ -1122,6 +1128,9 @@
11221128
{
11231129
"Member": "static Microsoft.EntityFrameworkCore.Metadata.ConfigurationSource? GetIsTemporalConfigurationSource(this Microsoft.EntityFrameworkCore.Metadata.IConventionEntityType entityType);"
11241130
},
1131+
{
1132+
"Member": "static Microsoft.EntityFrameworkCore.Metadata.ConfigurationSource? GetIsTemporalPeriodColumnsHiddenConfigurationSource(this Microsoft.EntityFrameworkCore.Metadata.IConventionEntityType entityType);"
1133+
},
11251134
{
11261135
"Member": "static string? GetPeriodEndPropertyName(this Microsoft.EntityFrameworkCore.Metadata.IReadOnlyEntityType entityType);"
11271136
},
@@ -1155,6 +1164,9 @@
11551164
{
11561165
"Member": "static bool IsTemporal(this Microsoft.EntityFrameworkCore.Metadata.IReadOnlyEntityType entityType);"
11571166
},
1167+
{
1168+
"Member": "static bool IsTemporalPeriodColumnsHidden(this Microsoft.EntityFrameworkCore.Metadata.IReadOnlyEntityType entityType);"
1169+
},
11581170
{
11591171
"Member": "static void SetHistoryTableName(this Microsoft.EntityFrameworkCore.Metadata.IMutableEntityType entityType, string? historyTableName);"
11601172
},
@@ -1179,6 +1191,12 @@
11791191
{
11801192
"Member": "static bool? SetIsTemporal(this Microsoft.EntityFrameworkCore.Metadata.IConventionEntityType entityType, bool? temporal, bool fromDataAnnotation = false);"
11811193
},
1194+
{
1195+
"Member": "static void SetIsTemporalPeriodColumnsHidden(this Microsoft.EntityFrameworkCore.Metadata.IMutableEntityType entityType, bool? hidden);"
1196+
},
1197+
{
1198+
"Member": "static bool? SetIsTemporalPeriodColumnsHidden(this Microsoft.EntityFrameworkCore.Metadata.IConventionEntityType entityType, bool? hidden, bool fromDataAnnotation = false);"
1199+
},
11821200
{
11831201
"Member": "static void SetPeriodEndPropertyName(this Microsoft.EntityFrameworkCore.Metadata.IMutableEntityType entityType, string? periodEndPropertyName);"
11841202
},
@@ -3049,6 +3067,9 @@
30493067
{
30503068
"Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Builders.TemporalPeriodPropertyBuilder HasPeriodStart(string propertyName);"
30513069
},
3070+
{
3071+
"Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Builders.TemporalTableBuilder PeriodColumnsHidden(bool hidden = true);"
3072+
},
30523073
{
30533074
"Member": "override string? ToString();"
30543075
},
@@ -3069,6 +3090,9 @@
30693090
{
30703091
"Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Builders.TemporalPeriodPropertyBuilder HasPeriodStart(System.Linq.Expressions.Expression<System.Func<TEntity, System.DateTime>> propertyExpression);"
30713092
},
3093+
{
3094+
"Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Builders.TemporalTableBuilder<TEntity> PeriodColumnsHidden(bool hidden = true);"
3095+
},
30723096
{
30733097
"Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Builders.TemporalTableBuilder<TEntity> UseHistoryTable(string name);"
30743098
},

src/EFCore.SqlServer/Extensions/SqlServerEntityTypeExtensions.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,53 @@ public static void SetHistoryTableSchema(this IMutableEntityType entityType, str
297297
return (string.IsNullOrEmpty(schema) ? "" : schema + ".") + historyTableName;
298298
}
299299

300+
/// <summary>
301+
/// Returns a value indicating whether the period columns of the entity type mapped to a temporal table are
302+
/// defined with the HIDDEN flag, which excludes them from <c>SELECT *</c> results.
303+
/// </summary>
304+
/// <remarks>
305+
/// The default value is <see langword="true" />, matching the behavior of EF Core releases prior to this option
306+
/// being introduced. Set to <see langword="false" /> to make the period columns visible.
307+
/// </remarks>
308+
/// <param name="entityType">The entity type.</param>
309+
/// <returns><see langword="true" /> if the period columns are hidden; otherwise <see langword="false" />.</returns>
310+
public static bool IsTemporalPeriodColumnsHidden(this IReadOnlyEntityType entityType)
311+
=> entityType[SqlServerAnnotationNames.TemporalPeriodColumnsHidden] as bool? ?? true;
312+
313+
/// <summary>
314+
/// Sets a value indicating whether the period columns of the entity type mapped to a temporal table are
315+
/// defined with the HIDDEN flag.
316+
/// </summary>
317+
/// <param name="entityType">The entity type.</param>
318+
/// <param name="hidden">The value to set; <see langword="null" /> to remove the explicit configuration.</param>
319+
public static void SetIsTemporalPeriodColumnsHidden(this IMutableEntityType entityType, bool? hidden)
320+
=> entityType.SetOrRemoveAnnotation(SqlServerAnnotationNames.TemporalPeriodColumnsHidden, hidden);
321+
322+
/// <summary>
323+
/// Sets a value indicating whether the period columns of the entity type mapped to a temporal table are
324+
/// defined with the HIDDEN flag.
325+
/// </summary>
326+
/// <param name="entityType">The entity type.</param>
327+
/// <param name="hidden">The value to set; <see langword="null" /> to remove the explicit configuration.</param>
328+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
329+
/// <returns>The configured value.</returns>
330+
public static bool? SetIsTemporalPeriodColumnsHidden(
331+
this IConventionEntityType entityType,
332+
bool? hidden,
333+
bool fromDataAnnotation = false)
334+
=> (bool?)entityType.SetOrRemoveAnnotation(
335+
SqlServerAnnotationNames.TemporalPeriodColumnsHidden,
336+
hidden,
337+
fromDataAnnotation)?.Value;
338+
339+
/// <summary>
340+
/// Gets the configuration source for the period-columns-hidden setting.
341+
/// </summary>
342+
/// <param name="entityType">The entity type.</param>
343+
/// <returns>The configuration source for the period-columns-hidden setting.</returns>
344+
public static ConfigurationSource? GetIsTemporalPeriodColumnsHiddenConfigurationSource(this IConventionEntityType entityType)
345+
=> entityType.FindAnnotation(SqlServerAnnotationNames.TemporalPeriodColumnsHidden)?.GetConfigurationSource();
346+
300347
#endregion Temporal table
301348

302349
#region SQL OUTPUT clause

src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,29 @@ public virtual OwnedNavigationTemporalPeriodPropertyBuilder HasPeriodEnd(string
9191
#pragma warning restore EF1001 // Internal EF Core API usage.
9292
}
9393

94+
/// <summary>
95+
/// Configures whether the period columns of the temporal table are defined with the HIDDEN flag,
96+
/// which excludes them from <c>SELECT *</c> results.
97+
/// </summary>
98+
/// <remarks>
99+
/// <para>
100+
/// The default value is <see langword="true" />, matching the behavior of EF Core releases prior to this option
101+
/// being introduced. Set to <see langword="false" /> to make the period columns visible in <c>SELECT *</c>.
102+
/// </para>
103+
/// <para>
104+
/// See <see href="https://aka.ms/efcore-docs-temporal">Using SQL Server temporal tables with EF Core</see>
105+
/// for more information.
106+
/// </para>
107+
/// </remarks>
108+
/// <param name="hidden">A value indicating whether the period columns should be hidden.</param>
109+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
110+
public virtual OwnedNavigationTemporalTableBuilder PeriodColumnsHidden(bool hidden = true)
111+
{
112+
_referenceOwnershipBuilder.OwnedEntityType.SetIsTemporalPeriodColumnsHidden(hidden);
113+
114+
return this;
115+
}
116+
94117
private IMutableProperty ConfigurePeriodProperty(string propertyName)
95118
{
96119
// TODO: Configure the property explicitly, but remove it if it's no longer used, issue #15898

src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder``.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,23 @@ public virtual OwnedNavigationTemporalPeriodPropertyBuilder HasPeriodStart(
8181
public virtual OwnedNavigationTemporalPeriodPropertyBuilder HasPeriodEnd(
8282
Expression<Func<TDependentEntity, DateTime>> propertyExpression)
8383
=> HasPeriodEnd(Check.NotNull(propertyExpression).GetMemberAccess().Name);
84+
85+
/// <summary>
86+
/// Configures whether the period columns of the temporal table are defined with the HIDDEN flag,
87+
/// which excludes them from <c>SELECT *</c> results.
88+
/// </summary>
89+
/// <remarks>
90+
/// <para>
91+
/// The default value is <see langword="true" />, matching the behavior of EF Core releases prior to this option
92+
/// being introduced. Set to <see langword="false" /> to make the period columns visible in <c>SELECT *</c>.
93+
/// </para>
94+
/// <para>
95+
/// See <see href="https://aka.ms/efcore-docs-temporal">Using SQL Server temporal tables with EF Core</see>
96+
/// for more information.
97+
/// </para>
98+
/// </remarks>
99+
/// <param name="hidden">A value indicating whether the period columns should be hidden.</param>
100+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
101+
public new virtual OwnedNavigationTemporalTableBuilder<TOwnerEntity, TDependentEntity> PeriodColumnsHidden(bool hidden = true)
102+
=> (OwnedNavigationTemporalTableBuilder<TOwnerEntity, TDependentEntity>)base.PeriodColumnsHidden(hidden);
84103
}

src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,29 @@ public virtual TemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName)
9191
#pragma warning restore EF1001 // Internal EF Core API usage.
9292
}
9393

94+
/// <summary>
95+
/// Configures whether the period columns of the temporal table are defined with the HIDDEN flag,
96+
/// which excludes them from <c>SELECT *</c> results.
97+
/// </summary>
98+
/// <remarks>
99+
/// <para>
100+
/// The default value is <see langword="true" />, matching the behavior of EF Core releases prior to this option
101+
/// being introduced. Set to <see langword="false" /> to make the period columns visible in <c>SELECT *</c>.
102+
/// </para>
103+
/// <para>
104+
/// See <see href="https://aka.ms/efcore-docs-temporal">Using SQL Server temporal tables with EF Core</see>
105+
/// for more information and examples.
106+
/// </para>
107+
/// </remarks>
108+
/// <param name="hidden">A value indicating whether the period columns should be hidden.</param>
109+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
110+
public virtual TemporalTableBuilder PeriodColumnsHidden(bool hidden = true)
111+
{
112+
_entityTypeBuilder.Metadata.SetIsTemporalPeriodColumnsHidden(hidden);
113+
114+
return this;
115+
}
116+
94117
private IMutableProperty ConfigurePeriodProperty(string propertyName)
95118
{
96119
// TODO: Configure the property explicitly, but remove it if it's no longer used, issue #15898

src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder`.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,23 @@ public virtual TemporalPeriodPropertyBuilder HasPeriodStart(Expression<Func<TEnt
7777
/// <returns>An object that can be used to configure the period end property.</returns>
7878
public virtual TemporalPeriodPropertyBuilder HasPeriodEnd(Expression<Func<TEntity, DateTime>> propertyExpression)
7979
=> HasPeriodEnd(Check.NotNull(propertyExpression).GetMemberAccess().Name);
80+
81+
/// <summary>
82+
/// Configures whether the period columns of the temporal table are defined with the HIDDEN flag,
83+
/// which excludes them from <c>SELECT *</c> results.
84+
/// </summary>
85+
/// <remarks>
86+
/// <para>
87+
/// The default value is <see langword="true" />, matching the behavior of EF Core releases prior to this option
88+
/// being introduced. Set to <see langword="false" /> to make the period columns visible in <c>SELECT *</c>.
89+
/// </para>
90+
/// <para>
91+
/// See <see href="https://aka.ms/efcore-docs-temporal">Using SQL Server temporal tables with EF Core</see>
92+
/// for more information and examples.
93+
/// </para>
94+
/// </remarks>
95+
/// <param name="hidden">A value indicating whether the period columns should be hidden.</param>
96+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
97+
public new virtual TemporalTableBuilder<TEntity> PeriodColumnsHidden(bool hidden = true)
98+
=> (TemporalTableBuilder<TEntity>)base.PeriodColumnsHidden(hidden);
8099
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,14 @@ public static class SqlServerAnnotationNames
299299
/// </summary>
300300
public const string TemporalIsPeriodEndColumn = Prefix + "TemporalIsPeriodEndColumn";
301301

302+
/// <summary>
303+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
304+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
305+
/// any release. You should only use it directly in your code with extreme caution and knowing that
306+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
307+
/// </summary>
308+
public const string TemporalPeriodColumnsHidden = Prefix + "TemporalPeriodColumnsHidden";
309+
302310
/// <summary>
303311
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
304312
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ public override IEnumerable<IAnnotation> For(ITable table, bool designTime)
130130

131131
yield return new Annotation(SqlServerAnnotationNames.TemporalPeriodEndColumnName, periodEndColumnName);
132132
}
133+
134+
if (!entityType.IsTemporalPeriodColumnsHidden())
135+
{
136+
yield return new Annotation(SqlServerAnnotationNames.TemporalPeriodColumnsHidden, false);
137+
}
133138
}
134139
}
135140

@@ -351,10 +356,20 @@ public override IEnumerable<IAnnotation> For(IColumn column, bool designTime)
351356
if (column.Name == periodStartColumnName)
352357
{
353358
yield return new Annotation(SqlServerAnnotationNames.TemporalIsPeriodStartColumn, true);
359+
360+
if (!entityType.IsTemporalPeriodColumnsHidden())
361+
{
362+
yield return new Annotation(SqlServerAnnotationNames.TemporalPeriodColumnsHidden, false);
363+
}
354364
}
355365
else if (column.Name == periodEndColumnName)
356366
{
357367
yield return new Annotation(SqlServerAnnotationNames.TemporalIsPeriodEndColumn, true);
368+
369+
if (!entityType.IsTemporalPeriodColumnsHidden())
370+
{
371+
yield return new Annotation(SqlServerAnnotationNames.TemporalPeriodColumnsHidden, false);
372+
}
358373
}
359374
}
360375
}

0 commit comments

Comments
 (0)