Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Temporal table support for owned and table splitting scenarios #26935

Merged
merged 1 commit into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static EntityTypeBuilder ToTable(
{
Check.NotNull(buildAction, nameof(buildAction));

buildAction(new TableBuilder(entityTypeBuilder.Metadata));
buildAction(new TableBuilder(entityTypeBuilder));

return entityTypeBuilder;
}
Expand All @@ -76,7 +76,7 @@ public static EntityTypeBuilder ToTable(

entityTypeBuilder.Metadata.SetTableName(name);
entityTypeBuilder.Metadata.SetSchema(null);
buildAction(new TableBuilder(entityTypeBuilder.Metadata));
buildAction(new TableBuilder(entityTypeBuilder));

return entityTypeBuilder;
}
Expand Down Expand Up @@ -114,7 +114,7 @@ public static EntityTypeBuilder<TEntity> ToTable<TEntity>(
{
Check.NotNull(buildAction, nameof(buildAction));

buildAction(new TableBuilder<TEntity>(null, null, entityTypeBuilder.Metadata));
buildAction(new TableBuilder<TEntity>(null, null, entityTypeBuilder));

return entityTypeBuilder;
}
Expand All @@ -141,7 +141,7 @@ public static EntityTypeBuilder<TEntity> ToTable<TEntity>(

entityTypeBuilder.Metadata.SetTableName(name);
entityTypeBuilder.Metadata.SetSchema(null);
buildAction(new TableBuilder<TEntity>(name, null, entityTypeBuilder.Metadata));
buildAction(new TableBuilder<TEntity>(name, null, entityTypeBuilder));

return entityTypeBuilder;
}
Expand Down Expand Up @@ -192,7 +192,7 @@ public static EntityTypeBuilder ToTable(

entityTypeBuilder.Metadata.SetTableName(name);
entityTypeBuilder.Metadata.SetSchema(schema);
buildAction(new TableBuilder(entityTypeBuilder.Metadata));
buildAction(new TableBuilder(entityTypeBuilder));

return entityTypeBuilder;
}
Expand Down Expand Up @@ -240,7 +240,7 @@ public static EntityTypeBuilder<TEntity> ToTable<TEntity>(

entityTypeBuilder.Metadata.SetTableName(name);
entityTypeBuilder.Metadata.SetSchema(schema);
buildAction(new TableBuilder<TEntity>(name, schema, entityTypeBuilder.Metadata));
buildAction(new TableBuilder<TEntity>(name, schema, entityTypeBuilder));

return entityTypeBuilder;
}
Expand Down Expand Up @@ -277,11 +277,11 @@ public static OwnedNavigationBuilder ToTable(
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static OwnedNavigationBuilder ToTable(
this OwnedNavigationBuilder referenceOwnershipBuilder,
Action<TableBuilder> buildAction)
Action<OwnedNavigationTableBuilder> buildAction)
{
Check.NotNull(buildAction, nameof(buildAction));

buildAction(new TableBuilder(referenceOwnershipBuilder.OwnedEntityType));
buildAction(new OwnedNavigationTableBuilder(referenceOwnershipBuilder));

return referenceOwnershipBuilder;
}
Expand All @@ -297,13 +297,13 @@ public static OwnedNavigationBuilder ToTable(
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwnerEntity, TRelatedEntity>(
this OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> referenceOwnershipBuilder,
Action<TableBuilder<TRelatedEntity>> buildAction)
Action<OwnedNavigationTableBuilder<TRelatedEntity>> buildAction)
where TOwnerEntity : class
where TRelatedEntity : class
{
Check.NotNull(buildAction, nameof(buildAction));

buildAction(new TableBuilder<TRelatedEntity>(null, null, referenceOwnershipBuilder.OwnedEntityType));
buildAction(new OwnedNavigationTableBuilder<TRelatedEntity>(referenceOwnershipBuilder));

return referenceOwnershipBuilder;
}
Expand Down Expand Up @@ -337,14 +337,14 @@ public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwne
public static OwnedNavigationBuilder ToTable(
this OwnedNavigationBuilder referenceOwnershipBuilder,
string? name,
Action<TableBuilder> buildAction)
Action<OwnedNavigationTableBuilder> buildAction)
{
Check.NullButNotEmpty(name, nameof(name));
Check.NotNull(buildAction, nameof(buildAction));

referenceOwnershipBuilder.OwnedEntityType.SetTableName(name);
referenceOwnershipBuilder.OwnedEntityType.SetSchema(null);
buildAction(new TableBuilder(referenceOwnershipBuilder.OwnedEntityType));
buildAction(new OwnedNavigationTableBuilder(referenceOwnershipBuilder));

return referenceOwnershipBuilder;
}
Expand All @@ -362,7 +362,7 @@ public static OwnedNavigationBuilder ToTable(
public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwnerEntity, TRelatedEntity>(
this OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> referenceOwnershipBuilder,
string? name,
Action<TableBuilder<TRelatedEntity>> buildAction)
Action<OwnedNavigationTableBuilder<TRelatedEntity>> buildAction)
where TOwnerEntity : class
where TRelatedEntity : class
{
Expand All @@ -371,7 +371,7 @@ public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwne

referenceOwnershipBuilder.OwnedEntityType.SetTableName(name);
referenceOwnershipBuilder.OwnedEntityType.SetSchema(null);
buildAction(new TableBuilder<TRelatedEntity>(name, null, referenceOwnershipBuilder.OwnedEntityType));
buildAction(new OwnedNavigationTableBuilder<TRelatedEntity>(referenceOwnershipBuilder));

return referenceOwnershipBuilder;
}
Expand Down Expand Up @@ -415,15 +415,15 @@ public static OwnedNavigationBuilder ToTable(
this OwnedNavigationBuilder referenceOwnershipBuilder,
string name,
string? schema,
Action<TableBuilder> buildAction)
Action<OwnedNavigationTableBuilder> buildAction)
{
Check.NotNull(name, nameof(name));
Check.NullButNotEmpty(schema, nameof(schema));
Check.NotNull(buildAction, nameof(buildAction));

referenceOwnershipBuilder.OwnedEntityType.SetTableName(name);
referenceOwnershipBuilder.OwnedEntityType.SetSchema(schema);
buildAction(new TableBuilder(referenceOwnershipBuilder.OwnedEntityType));
buildAction(new OwnedNavigationTableBuilder(referenceOwnershipBuilder));

return referenceOwnershipBuilder;
}
Expand Down Expand Up @@ -462,7 +462,7 @@ public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwne
this OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> referenceOwnershipBuilder,
string name,
string? schema,
Action<TableBuilder<TRelatedEntity>> buildAction)
Action<OwnedNavigationTableBuilder<TRelatedEntity>> buildAction)
where TOwnerEntity : class
where TRelatedEntity : class
{
Expand All @@ -472,7 +472,7 @@ public static OwnedNavigationBuilder<TOwnerEntity, TRelatedEntity> ToTable<TOwne

referenceOwnershipBuilder.OwnedEntityType.SetTableName(name);
referenceOwnershipBuilder.OwnedEntityType.SetSchema(schema);
buildAction(new TableBuilder<TRelatedEntity>(name, schema, referenceOwnershipBuilder.OwnedEntityType));
buildAction(new OwnedNavigationTableBuilder<TRelatedEntity>(referenceOwnershipBuilder));

return referenceOwnershipBuilder;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;

namespace Microsoft.EntityFrameworkCore.Metadata.Builders;

/// <summary>
/// Instances of this class are returned from methods when using the <see cref="ModelBuilder" /> API
/// and it is not designed to be directly constructed in your application code.
/// </summary>
public class OwnedNavigationTableBuilder
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
public OwnedNavigationTableBuilder(OwnedNavigationBuilder ownedNavigationBuilder)
{
OwnedNavigationBuilder = ownedNavigationBuilder;
}

/// <summary>
/// The entity type being configured.
/// </summary>
public virtual IMutableEntityType Metadata => OwnedNavigationBuilder.OwnedEntityType;

/// <summary>
/// The entity type builder.
/// </summary>
public virtual OwnedNavigationBuilder OwnedNavigationBuilder { get; }

/// <summary>
/// Configures the table to be ignored by migrations.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-migrations">Database migrations</see> for more information.
/// </remarks>
/// <param name="excluded">A value indicating whether the table should be managed by migrations.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public virtual OwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true)
{
Metadata.SetIsTableExcludedFromMigrations(excluded);

return this;
}

#region Hidden System.Object members

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString()
=> base.ToString();

/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><see langword="true" /> if the specified object is equal to the current object; otherwise, <see langword="false" />.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj)
=> base.Equals(obj);

/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode()
=> base.GetHashCode();

#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Metadata.Builders;

/// <summary>
/// Instances of this class are returned from methods when using the <see cref="ModelBuilder" /> API
/// and it is not designed to be directly constructed in your application code.
/// </summary>
/// <typeparam name="TEntity">The entity type being configured.</typeparam>
public class OwnedNavigationTableBuilder<TEntity> : OwnedNavigationTableBuilder
where TEntity : class
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
public OwnedNavigationTableBuilder(OwnedNavigationBuilder referenceOwnershipBuilder)
: base(referenceOwnershipBuilder)
{
}

/// <summary>
/// Configures the table to be ignored by migrations.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-migrations">Database migrations</see> for more information.
/// </remarks>
/// <param name="excluded">A value indicating whether the table should be managed by migrations.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public new virtual OwnedNavigationTableBuilder<TEntity> ExcludeFromMigrations(bool excluded = true)
=> (OwnedNavigationTableBuilder<TEntity>)base.ExcludeFromMigrations(excluded);
}
11 changes: 8 additions & 3 deletions src/EFCore.Relational/Metadata/Builders/TableBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,20 @@ public class TableBuilder
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
public TableBuilder(IMutableEntityType entityType)
public TableBuilder(EntityTypeBuilder entityTypeBuilder)
{
Metadata = entityType;
EntityTypeBuilder = entityTypeBuilder;
}

/// <summary>
/// The entity type being configured.
/// </summary>
public virtual IMutableEntityType Metadata { get; }
public virtual IMutableEntityType Metadata => EntityTypeBuilder.Metadata;

/// <summary>
/// The entity type builder.
/// </summary>
public virtual EntityTypeBuilder EntityTypeBuilder { get; }

/// <summary>
/// Configures the table to be ignored by migrations.
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ public class TableBuilder<TEntity> : TableBuilder
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
public TableBuilder(string? name, string? schema, IMutableEntityType entityType)
: base(entityType)
public TableBuilder(string? name, string? schema, EntityTypeBuilder entityTypeBuilder)
: base(entityTypeBuilder)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1080,11 +1080,16 @@ protected override Expression VisitExtension(Expression extensionExpression)
return null;
}

var entityProjectionExpression = GetEntityProjectionExpression(entityShaperExpression);
var foreignKey = navigation.ForeignKey;
if (navigation.IsCollection)
{
var innerShapedQuery = CreateShapedQueryExpression(
targetEntityType, _sqlExpressionFactory.Select(targetEntityType));
var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
entityProjectionExpression,
navigation);

var innerShapedQuery = CreateShapedQueryExpression(
targetEntityType, innerSelectExpression);

var makeNullable = foreignKey.PrincipalKey.Properties
.Concat(foreignKey.Properties)
Expand Down Expand Up @@ -1132,7 +1137,6 @@ outerKey is NewArrayExpression newArrayExpression
Expression.Quote(correlationPredicate));
}

var entityProjectionExpression = GetEntityProjectionExpression(entityShaperExpression);
var innerShaper = entityProjectionExpression.BindNavigation(navigation);
if (innerShaper == null)
{
Expand Down Expand Up @@ -1175,7 +1179,10 @@ outerKey is NewArrayExpression newArrayExpression
// Owned types don't support inheritance See https://github.com/dotnet/efcore/issues/9630
// So there is no handling for dependent having TPT
table = targetEntityType.GetViewOrTableMappings().Single().Table;
var innerSelectExpression = _sqlExpressionFactory.Select(targetEntityType);
var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
entityProjectionExpression,
navigation);

var innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression);

var makeNullable = foreignKey.PrincipalKey.Properties
Expand Down Expand Up @@ -1231,6 +1238,50 @@ outerKey is NewArrayExpression newArrayExpression
targetEntityType,
(ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression,
navigation);

SelectExpression BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
EntityProjectionExpression entityProjectionExpression,
maumar marked this conversation as resolved.
Show resolved Hide resolved
INavigation navigation)
{
// just need any column - we use it only to extract the table it originated from
var sourceColumn = entityProjectionExpression
.BindProperty(
navigation.IsOnDependent
? foreignKey.Properties[0]
: foreignKey.PrincipalKey.Properties[0]);

var sourceTable = FindRootTableExpressionForColumn(sourceColumn);
var ownedTable = new TableExpression(targetEntityType.GetTableMappings().Single().Table);

foreach (var annotation in sourceTable.GetAnnotations())
{
ownedTable.SetAnnotation(annotation.Name, annotation.Value);
}

return _sqlExpressionFactory.Select(targetEntityType, ownedTable);
}

static TableExpressionBase FindRootTableExpressionForColumn(ColumnExpression column)
{
var table = column.Table;
if (table is JoinExpressionBase joinExpressionBase)
{
table = joinExpressionBase.Table;
}
else if (table is SetOperationBase setOperationBase)
{
table = setOperationBase.Source1;
}

if (table is SelectExpression selectExpression)
{
var matchingProjection = (ColumnExpression)selectExpression.Projection.Where(p => p.Alias == column.Name).Single().Expression;

return FindRootTableExpressionForColumn(matchingProjection);
}

return table;
}
}

private static Expression AddConvertToObject(Expression expression)
Expand Down
Loading