Skip to content

Commit 2dec3c8

Browse files
committed
Temporal table support for owned and table splitting scenarios
Fix to #26858 - Query: improve TableExpressionBase extensibility by adding annotations Fix to #26469 - Query: enable temporal tables for table splitting scenarios Fix to #26451 - Temporal Table: Owned Entities support? Added annotation infra for TableExpressionBase and annotations for temporal table information. Removed (now unnecessary) temporal specific table expressions. Also added temporal table support for owned typed and table splitting in general using the annotations to store the temporal information (no need for provider specific logic in places where we didn't have good extensibility) For table splitting, every entity must explicitly define period start/end columns. They all need to be the same, but if not explicitly provided we try to uniquefy them. We should fix this so that you only need to specify it in one place but it's currently hard to do (hopefully convention layering will make it easier) Fixes #26858 Fixes #26469 Fixes #26451
1 parent a778126 commit 2dec3c8

File tree

61 files changed

+7724
-634
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+7724
-634
lines changed

src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs

+18-18
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public static EntityTypeBuilder ToTable(
5151
{
5252
Check.NotNull(buildAction, nameof(buildAction));
5353

54-
buildAction(new TableBuilder(entityTypeBuilder.Metadata));
54+
buildAction(new TableBuilder(entityTypeBuilder));
5555

5656
return entityTypeBuilder;
5757
}
@@ -76,7 +76,7 @@ public static EntityTypeBuilder ToTable(
7676

7777
entityTypeBuilder.Metadata.SetTableName(name);
7878
entityTypeBuilder.Metadata.SetSchema(null);
79-
buildAction(new TableBuilder(entityTypeBuilder.Metadata));
79+
buildAction(new TableBuilder(entityTypeBuilder));
8080

8181
return entityTypeBuilder;
8282
}
@@ -114,7 +114,7 @@ public static EntityTypeBuilder<TEntity> ToTable<TEntity>(
114114
{
115115
Check.NotNull(buildAction, nameof(buildAction));
116116

117-
buildAction(new TableBuilder<TEntity>(null, null, entityTypeBuilder.Metadata));
117+
buildAction(new TableBuilder<TEntity>(entityTypeBuilder));
118118

119119
return entityTypeBuilder;
120120
}
@@ -141,7 +141,7 @@ public static EntityTypeBuilder<TEntity> ToTable<TEntity>(
141141

142142
entityTypeBuilder.Metadata.SetTableName(name);
143143
entityTypeBuilder.Metadata.SetSchema(null);
144-
buildAction(new TableBuilder<TEntity>(name, null, entityTypeBuilder.Metadata));
144+
buildAction(new TableBuilder<TEntity>(entityTypeBuilder));
145145

146146
return entityTypeBuilder;
147147
}
@@ -192,7 +192,7 @@ public static EntityTypeBuilder ToTable(
192192

193193
entityTypeBuilder.Metadata.SetTableName(name);
194194
entityTypeBuilder.Metadata.SetSchema(schema);
195-
buildAction(new TableBuilder(entityTypeBuilder.Metadata));
195+
buildAction(new TableBuilder(entityTypeBuilder));
196196

197197
return entityTypeBuilder;
198198
}
@@ -240,7 +240,7 @@ public static EntityTypeBuilder<TEntity> ToTable<TEntity>(
240240

241241
entityTypeBuilder.Metadata.SetTableName(name);
242242
entityTypeBuilder.Metadata.SetSchema(schema);
243-
buildAction(new TableBuilder<TEntity>(name, schema, entityTypeBuilder.Metadata));
243+
buildAction(new TableBuilder<TEntity>(entityTypeBuilder));
244244

245245
return entityTypeBuilder;
246246
}
@@ -277,11 +277,11 @@ public static OwnedNavigationBuilder ToTable(
277277
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
278278
public static OwnedNavigationBuilder ToTable(
279279
this OwnedNavigationBuilder referenceOwnershipBuilder,
280-
Action<TableBuilder> buildAction)
280+
Action<OwnedNavigationTableBuilder> buildAction)
281281
{
282282
Check.NotNull(buildAction, nameof(buildAction));
283283

284-
buildAction(new TableBuilder(referenceOwnershipBuilder.OwnedEntityType));
284+
buildAction(new OwnedNavigationTableBuilder(referenceOwnershipBuilder));
285285

286286
return referenceOwnershipBuilder;
287287
}
@@ -297,13 +297,13 @@ public static OwnedNavigationBuilder ToTable(
297297
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
298298
public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwnerEntity, TRelatedEntity>(
299299
this OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> referenceOwnershipBuilder,
300-
Action<TableBuilder<TRelatedEntity>> buildAction)
300+
Action<OwnedNavigationTableBuilder<TRelatedEntity>> buildAction)
301301
where TOwnerEntity : class
302302
where TRelatedEntity : class
303303
{
304304
Check.NotNull(buildAction, nameof(buildAction));
305305

306-
buildAction(new TableBuilder<TRelatedEntity>(null, null, referenceOwnershipBuilder.OwnedEntityType));
306+
buildAction(new OwnedNavigationTableBuilder<TRelatedEntity>(referenceOwnershipBuilder));
307307

308308
return referenceOwnershipBuilder;
309309
}
@@ -337,14 +337,14 @@ public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwne
337337
public static OwnedNavigationBuilder ToTable(
338338
this OwnedNavigationBuilder referenceOwnershipBuilder,
339339
string? name,
340-
Action<TableBuilder> buildAction)
340+
Action<OwnedNavigationTableBuilder> buildAction)
341341
{
342342
Check.NullButNotEmpty(name, nameof(name));
343343
Check.NotNull(buildAction, nameof(buildAction));
344344

345345
referenceOwnershipBuilder.OwnedEntityType.SetTableName(name);
346346
referenceOwnershipBuilder.OwnedEntityType.SetSchema(null);
347-
buildAction(new TableBuilder(referenceOwnershipBuilder.OwnedEntityType));
347+
buildAction(new OwnedNavigationTableBuilder(referenceOwnershipBuilder));
348348

349349
return referenceOwnershipBuilder;
350350
}
@@ -362,7 +362,7 @@ public static OwnedNavigationBuilder ToTable(
362362
public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwnerEntity, TRelatedEntity>(
363363
this OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> referenceOwnershipBuilder,
364364
string? name,
365-
Action<TableBuilder<TRelatedEntity>> buildAction)
365+
Action<OwnedNavigationTableBuilder<TRelatedEntity>> buildAction)
366366
where TOwnerEntity : class
367367
where TRelatedEntity : class
368368
{
@@ -371,7 +371,7 @@ public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwne
371371

372372
referenceOwnershipBuilder.OwnedEntityType.SetTableName(name);
373373
referenceOwnershipBuilder.OwnedEntityType.SetSchema(null);
374-
buildAction(new TableBuilder<TRelatedEntity>(name, null, referenceOwnershipBuilder.OwnedEntityType));
374+
buildAction(new OwnedNavigationTableBuilder<TRelatedEntity>(referenceOwnershipBuilder));
375375

376376
return referenceOwnershipBuilder;
377377
}
@@ -415,15 +415,15 @@ public static OwnedNavigationBuilder ToTable(
415415
this OwnedNavigationBuilder referenceOwnershipBuilder,
416416
string name,
417417
string? schema,
418-
Action<TableBuilder> buildAction)
418+
Action<OwnedNavigationTableBuilder> buildAction)
419419
{
420420
Check.NotNull(name, nameof(name));
421421
Check.NullButNotEmpty(schema, nameof(schema));
422422
Check.NotNull(buildAction, nameof(buildAction));
423423

424424
referenceOwnershipBuilder.OwnedEntityType.SetTableName(name);
425425
referenceOwnershipBuilder.OwnedEntityType.SetSchema(schema);
426-
buildAction(new TableBuilder(referenceOwnershipBuilder.OwnedEntityType));
426+
buildAction(new OwnedNavigationTableBuilder(referenceOwnershipBuilder));
427427

428428
return referenceOwnershipBuilder;
429429
}
@@ -462,7 +462,7 @@ public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwne
462462
this OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> referenceOwnershipBuilder,
463463
string name,
464464
string? schema,
465-
Action<TableBuilder<TRelatedEntity>> buildAction)
465+
Action<OwnedNavigationTableBuilder<TRelatedEntity>> buildAction)
466466
where TOwnerEntity : class
467467
where TRelatedEntity : class
468468
{
@@ -472,7 +472,7 @@ public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwne
472472

473473
referenceOwnershipBuilder.OwnedEntityType.SetTableName(name);
474474
referenceOwnershipBuilder.OwnedEntityType.SetSchema(schema);
475-
buildAction(new TableBuilder<TRelatedEntity>(name, schema, referenceOwnershipBuilder.OwnedEntityType));
475+
buildAction(new OwnedNavigationTableBuilder<TRelatedEntity>(referenceOwnershipBuilder));
476476

477477
return referenceOwnershipBuilder;
478478
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.ComponentModel;
5+
6+
namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
7+
8+
/// <summary>
9+
/// Instances of this class are returned from methods when using the <see cref="ModelBuilder" /> API
10+
/// and it is not designed to be directly constructed in your application code.
11+
/// </summary>
12+
public class OwnedNavigationTableBuilder
13+
{
14+
/// <summary>
15+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
16+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
17+
/// any release. You should only use it directly in your code with extreme caution and knowing that
18+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
19+
/// </summary>
20+
[EntityFrameworkInternal]
21+
public OwnedNavigationTableBuilder(OwnedNavigationBuilder ownedNavigationBuilder)
22+
{
23+
OwnedNavigationBuilder = ownedNavigationBuilder;
24+
}
25+
26+
/// <summary>
27+
/// The entity type being configured.
28+
/// </summary>
29+
public virtual IMutableEntityType Metadata => OwnedNavigationBuilder.OwnedEntityType;
30+
31+
/// <summary>
32+
/// The entity type builder.
33+
/// </summary>
34+
public virtual OwnedNavigationBuilder OwnedNavigationBuilder { get; }
35+
36+
/// <summary>
37+
/// Configures the table to be ignored by migrations.
38+
/// </summary>
39+
/// <remarks>
40+
/// See <see href="https://aka.ms/efcore-docs-migrations">Database migrations</see> for more information.
41+
/// </remarks>
42+
/// <param name="excluded">A value indicating whether the table should be managed by migrations.</param>
43+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
44+
public virtual OwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true)
45+
{
46+
Metadata.SetIsTableExcludedFromMigrations(excluded);
47+
48+
return this;
49+
}
50+
51+
#region Hidden System.Object members
52+
53+
/// <summary>
54+
/// Returns a string that represents the current object.
55+
/// </summary>
56+
/// <returns>A string that represents the current object.</returns>
57+
[EditorBrowsable(EditorBrowsableState.Never)]
58+
public override string? ToString()
59+
=> base.ToString();
60+
61+
/// <summary>
62+
/// Determines whether the specified object is equal to the current object.
63+
/// </summary>
64+
/// <param name="obj">The object to compare with the current object.</param>
65+
/// <returns><see langword="true" /> if the specified object is equal to the current object; otherwise, <see langword="false" />.</returns>
66+
[EditorBrowsable(EditorBrowsableState.Never)]
67+
public override bool Equals(object? obj)
68+
=> base.Equals(obj);
69+
70+
/// <summary>
71+
/// Serves as the default hash function.
72+
/// </summary>
73+
/// <returns>A hash code for the current object.</returns>
74+
[EditorBrowsable(EditorBrowsableState.Never)]
75+
public override int GetHashCode()
76+
=> base.GetHashCode();
77+
78+
#endregion
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
5+
6+
/// <summary>
7+
/// Instances of this class are returned from methods when using the <see cref="ModelBuilder" /> API
8+
/// and it is not designed to be directly constructed in your application code.
9+
/// </summary>
10+
/// <typeparam name="TEntity">The entity type being configured.</typeparam>
11+
public class OwnedNavigationTableBuilder<TEntity> : OwnedNavigationTableBuilder
12+
where TEntity : class
13+
{
14+
/// <summary>
15+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
16+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
17+
/// any release. You should only use it directly in your code with extreme caution and knowing that
18+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
19+
/// </summary>
20+
[EntityFrameworkInternal]
21+
public OwnedNavigationTableBuilder(OwnedNavigationBuilder referenceOwnershipBuilder)
22+
: base(referenceOwnershipBuilder)
23+
{
24+
}
25+
26+
/// <summary>
27+
/// Configures the table to be ignored by migrations.
28+
/// </summary>
29+
/// <remarks>
30+
/// See <see href="https://aka.ms/efcore-docs-migrations">Database migrations</see> for more information.
31+
/// </remarks>
32+
/// <param name="excluded">A value indicating whether the table should be managed by migrations.</param>
33+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
34+
public new virtual OwnedNavigationTableBuilder<TEntity> ExcludeFromMigrations(bool excluded = true)
35+
=> (OwnedNavigationTableBuilder<TEntity>)base.ExcludeFromMigrations(excluded);
36+
}

src/EFCore.Relational/Metadata/Builders/TableBuilder.cs

+8-3
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,20 @@ public class TableBuilder
1818
/// doing so can result in application failures when updating to a new Entity Framework Core release.
1919
/// </summary>
2020
[EntityFrameworkInternal]
21-
public TableBuilder(IMutableEntityType entityType)
21+
public TableBuilder(EntityTypeBuilder entityTypeBuilder)
2222
{
23-
Metadata = entityType;
23+
EntityTypeBuilder = entityTypeBuilder;
2424
}
2525

2626
/// <summary>
2727
/// The entity type being configured.
2828
/// </summary>
29-
public virtual IMutableEntityType Metadata { get; }
29+
public virtual IMutableEntityType Metadata => EntityTypeBuilder.Metadata;
30+
31+
/// <summary>
32+
/// The entity type builder.
33+
/// </summary>
34+
public virtual EntityTypeBuilder EntityTypeBuilder { get; }
3035

3136
/// <summary>
3237
/// Configures the table to be ignored by migrations.

src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ public class TableBuilder<TEntity> : TableBuilder
1818
/// doing so can result in application failures when updating to a new Entity Framework Core release.
1919
/// </summary>
2020
[EntityFrameworkInternal]
21-
public TableBuilder(string? name, string? schema, IMutableEntityType entityType)
22-
: base(entityType)
21+
public TableBuilder(EntityTypeBuilder entityTypeBuilder)
22+
: base(entityTypeBuilder)
2323
{
2424
}
2525

src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs

+55-4
Original file line numberDiff line numberDiff line change
@@ -1080,11 +1080,16 @@ protected override Expression VisitExtension(Expression extensionExpression)
10801080
return null;
10811081
}
10821082

1083+
var entityProjectionExpression = GetEntityProjectionExpression(entityShaperExpression);
10831084
var foreignKey = navigation.ForeignKey;
10841085
if (navigation.IsCollection)
10851086
{
1086-
var innerShapedQuery = CreateShapedQueryExpression(
1087-
targetEntityType, _sqlExpressionFactory.Select(targetEntityType));
1087+
var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
1088+
entityProjectionExpression,
1089+
navigation);
1090+
1091+
var innerShapedQuery = CreateShapedQueryExpression(
1092+
targetEntityType, innerSelectExpression);
10881093

10891094
var makeNullable = foreignKey.PrincipalKey.Properties
10901095
.Concat(foreignKey.Properties)
@@ -1132,7 +1137,6 @@ outerKey is NewArrayExpression newArrayExpression
11321137
Expression.Quote(correlationPredicate));
11331138
}
11341139

1135-
var entityProjectionExpression = GetEntityProjectionExpression(entityShaperExpression);
11361140
var innerShaper = entityProjectionExpression.BindNavigation(navigation);
11371141
if (innerShaper == null)
11381142
{
@@ -1175,7 +1179,10 @@ outerKey is NewArrayExpression newArrayExpression
11751179
// Owned types don't support inheritance See https://github.com/dotnet/efcore/issues/9630
11761180
// So there is no handling for dependent having TPT
11771181
table = targetEntityType.GetViewOrTableMappings().Single().Table;
1178-
var innerSelectExpression = _sqlExpressionFactory.Select(targetEntityType);
1182+
var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
1183+
entityProjectionExpression,
1184+
navigation);
1185+
11791186
var innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression);
11801187

11811188
var makeNullable = foreignKey.PrincipalKey.Properties
@@ -1231,6 +1238,50 @@ outerKey is NewArrayExpression newArrayExpression
12311238
targetEntityType,
12321239
(ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression,
12331240
navigation);
1241+
1242+
SelectExpression BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
1243+
EntityProjectionExpression entityProjectionExpression,
1244+
INavigation navigation)
1245+
{
1246+
// just need any column - we use it only to extract the table it originated from
1247+
var sourceColumn = entityProjectionExpression
1248+
.BindProperty(
1249+
navigation.IsOnDependent
1250+
? foreignKey.Properties[0]
1251+
: foreignKey.PrincipalKey.Properties[0]);
1252+
1253+
var sourceTable = FindRootTableExpressionForColumn(sourceColumn);
1254+
var ownedTable = new TableExpression(targetEntityType.GetTableMappings().Single().Table);
1255+
1256+
foreach (var annotation in sourceTable.GetAnnotations())
1257+
{
1258+
ownedTable.SetAnnotation(annotation.Name, annotation.Value);
1259+
}
1260+
1261+
return _sqlExpressionFactory.Select(targetEntityType, ownedTable);
1262+
}
1263+
1264+
static TableExpressionBase FindRootTableExpressionForColumn(ColumnExpression column)
1265+
{
1266+
var table = column.Table;
1267+
if (table is JoinExpressionBase joinExpressionBase)
1268+
{
1269+
table = joinExpressionBase.Table;
1270+
}
1271+
else if (table is SetOperationBase setOperationBase)
1272+
{
1273+
table = setOperationBase.Source1;
1274+
}
1275+
1276+
if (table is SelectExpression selectExpression)
1277+
{
1278+
var matchingProjection = (ColumnExpression)selectExpression.Projection.Where(p => p.Alias == column.Name).Single().Expression;
1279+
1280+
return FindRootTableExpressionForColumn(matchingProjection);
1281+
}
1282+
1283+
return table;
1284+
}
12341285
}
12351286

12361287
private static Expression AddConvertToObject(Expression expression)

0 commit comments

Comments
 (0)