Skip to content

Commit

Permalink
Fix to #26451 - Temporal Table: Owned Entities support
Browse files Browse the repository at this point in the history
Added extensibility point to SharedTypeEntityExpandingExpressionVisitor so that we can create temporal table expressions representing owned entities, if their owner is also a temporal table expression.
Just like any other nav expansion, this only works for AsOf operation.

Also added internal interface to TableExpression, so that we can extract table metadata information (table name and schema) from provider specific table expressions.
  • Loading branch information
maumar committed Nov 23, 2021
1 parent 1a29900 commit 77f2d2d
Show file tree
Hide file tree
Showing 16 changed files with 774 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServi
{ typeof(ISqlExpressionFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IRelationalQueryStringFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IRelationalParameterBasedSqlProcessorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IRelationalSharedTypeEntityExpansionHelper), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IMigrationsModelDiffer), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IMigrationsSqlGenerator), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IMigrator), new ServiceCharacteristics(ServiceLifetime.Scoped) },
Expand Down Expand Up @@ -192,6 +193,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IRelationalParameterBasedSqlProcessorFactory, RelationalParameterBasedSqlProcessorFactory>();
TryAdd<IRelationalQueryStringFactory, RelationalQueryStringFactory>();
TryAdd<IQueryCompilationContextFactory, RelationalQueryCompilationContextFactory>();
TryAdd<IRelationalSharedTypeEntityExpansionHelper, RelationalSharedTypeEntityExpansionHelper>();

ServiceCollectionMap.GetInfrastructure()
.AddDependencySingleton<RelationalSqlGenerationHelperDependencies>()
Expand Down Expand Up @@ -227,7 +229,8 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
.AddDependencyScoped<RelationalConnectionDependencies>()
.AddDependencyScoped<RelationalDatabaseDependencies>()
.AddDependencyScoped<RelationalQueryContextDependencies>()
.AddDependencyScoped<RelationalQueryCompilationContextDependencies>();
.AddDependencyScoped<RelationalQueryCompilationContextDependencies>()
.AddDependencyScoped<RelationalSharedTypeEntityExpansionHelperDependencies>();

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

using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

namespace Microsoft.EntityFrameworkCore.Query.Internal
{
/// <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>
public interface IRelationalSharedTypeEntityExpansionHelper
{
/// <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>
public TableExpressionBase CreateRelatedTableExpression(
TableExpressionBase sourceTable,
IEntityType targetEntityType);
}
}
30 changes: 30 additions & 0 deletions src/EFCore.Relational/Query/Internal/ITableMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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.Query.Internal
{
/// <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>
public interface ITableMetadata
{
/// <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>
string Name { get; }

/// <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>
string? Schema { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

namespace Microsoft.EntityFrameworkCore.Query.Internal
{
/// <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>
public class RelationalSharedTypeEntityExpansionHelper : IRelationalSharedTypeEntityExpansionHelper
{
/// <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 RelationalSharedTypeEntityExpansionHelper(RelationalSharedTypeEntityExpansionHelperDependencies dependencies)
{
Dependencies = dependencies;
}

/// <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>
protected virtual RelationalSharedTypeEntityExpansionHelperDependencies Dependencies { get; }

/// <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 virtual TableExpressionBase CreateRelatedTableExpression(
TableExpressionBase sourceTable,
IEntityType targetEntityType)
{
var table = targetEntityType.GetTableMappings().Single().Table;

return new TableExpression(table);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ public RelationalQueryableMethodTranslatingExpressionVisitor(
_queryCompilationContext = queryCompilationContext;
_sqlTranslator = relationalDependencies.RelationalSqlTranslatingExpressionVisitorFactory.Create(queryCompilationContext, this);
_sharedTypeEntityExpandingExpressionVisitor =
new SharedTypeEntityExpandingExpressionVisitor(_sqlTranslator, sqlExpressionFactory);
new SharedTypeEntityExpandingExpressionVisitor(
_sqlTranslator,
sqlExpressionFactory,
relationalDependencies.RelationalSharedTypeEntityExpansionHelper);
_projectionBindingExpressionVisitor = new RelationalProjectionBindingExpressionVisitor(this, _sqlTranslator);
_sqlExpressionFactory = sqlExpressionFactory;
_subquery = false;
Expand All @@ -74,7 +77,11 @@ protected RelationalQueryableMethodTranslatingExpressionVisitor(
_sqlTranslator = RelationalDependencies.RelationalSqlTranslatingExpressionVisitorFactory.Create(
parentVisitor._queryCompilationContext, parentVisitor);
_sharedTypeEntityExpandingExpressionVisitor =
new SharedTypeEntityExpandingExpressionVisitor(_sqlTranslator, parentVisitor._sqlExpressionFactory);
new SharedTypeEntityExpandingExpressionVisitor(
_sqlTranslator,
parentVisitor._sqlExpressionFactory,
RelationalDependencies.RelationalSharedTypeEntityExpansionHelper);

_projectionBindingExpressionVisitor = new RelationalProjectionBindingExpressionVisitor(this, _sqlTranslator);
_sqlExpressionFactory = parentVisitor._sqlExpressionFactory;
_subquery = true;
Expand Down Expand Up @@ -1157,15 +1164,18 @@ private static readonly MethodInfo _objectEqualsMethodInfo

private readonly RelationalSqlTranslatingExpressionVisitor _sqlTranslator;
private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly IRelationalSharedTypeEntityExpansionHelper _sharedTypeEntityExpansionHelper;

private SelectExpression _selectExpression;

public SharedTypeEntityExpandingExpressionVisitor(
RelationalSqlTranslatingExpressionVisitor sqlTranslator,
ISqlExpressionFactory sqlExpressionFactory)
ISqlExpressionFactory sqlExpressionFactory,
IRelationalSharedTypeEntityExpansionHelper sharedTypeEntityExpansionHelper)
{
_sqlTranslator = sqlTranslator;
_sqlExpressionFactory = sqlExpressionFactory;
_sharedTypeEntityExpansionHelper = sharedTypeEntityExpansionHelper;
_selectExpression = null!;
}

Expand Down Expand Up @@ -1255,11 +1265,28 @@ protected override Expression VisitExtension(Expression extensionExpression)
return null;
}

var entityProjectionExpression = (EntityProjectionExpression)
(entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression
? _selectExpression.GetProjection(projectionBindingExpression)
: entityShaperExpression.ValueBufferExpression);

var useOldBehavior = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue26469", out var enabled)
&& enabled;

var foreignKey = navigation.ForeignKey;
if (navigation.IsCollection)
{
var innerShapedQuery = CreateShapedQueryExpression(
targetEntityType, _sqlExpressionFactory.Select(targetEntityType));
var innerShapedQuery = default(ShapedQueryExpression);

var innerSelectExpression = !useOldBehavior
? BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
entityProjectionExpression,
navigation,
foreignKey,
targetEntityType)
: _sqlExpressionFactory.Select(targetEntityType);

innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression);

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

var entityProjectionExpression = (EntityProjectionExpression)
(entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression
? _selectExpression.GetProjection(projectionBindingExpression)
: entityShaperExpression.ValueBufferExpression);

var innerShaper = entityProjectionExpression.BindNavigation(navigation);
if (innerShaper == null)
{
Expand Down Expand Up @@ -1354,7 +1376,21 @@ 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 = default(SelectExpression);
if (!useOldBehavior)
{
innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
entityProjectionExpression,
navigation,
foreignKey,
targetEntityType);
}
else
{
innerSelectExpression = _sqlExpressionFactory.Select(targetEntityType);
}

var innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression);

var makeNullable = foreignKey.PrincipalKey.Properties
Expand All @@ -1380,14 +1416,61 @@ outerKey is NewArrayExpression newArrayExpression
innerShaper = new RelationalEntityShaperExpression(
targetEntityType,
_selectExpression.GenerateWeakEntityProjectionExpression(
targetEntityType, table, null, leftJoinTable, nullable: true)!,
targetEntityType,
table,
null,
leftJoinTable,
nullable: true)!,
nullable: true);
}

entityProjectionExpression.AddNavigationBinding(navigation, innerShaper);
}

return innerShaper;

SelectExpression BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
EntityProjectionExpression entityProjectionExpression,
INavigation navigation,
IForeignKey foreignKey,
IEntityType targetEntityType)
{
// 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.Table, sourceColumn.Name);

var ownedTable = _sharedTypeEntityExpansionHelper.CreateRelatedTableExpression(
sourceTable,
targetEntityType);

return _sqlExpressionFactory.Select(targetEntityType, ownedTable);
}

static TableExpressionBase FindRootTableExpressionForColumn(TableExpressionBase table, string columnName)
{
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 == columnName).Single().Expression;

return FindRootTableExpressionForColumn(matchingProjection.Table, matchingProjection.Name);
}

return table;
}
}

private static Expression AddConvertToObject(Expression expression)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -54,13 +55,16 @@ public sealed record RelationalQueryableMethodTranslatingExpressionVisitorDepend
[EntityFrameworkInternal]
public RelationalQueryableMethodTranslatingExpressionVisitorDependencies(
IRelationalSqlTranslatingExpressionVisitorFactory relationalSqlTranslatingExpressionVisitorFactory,
ISqlExpressionFactory sqlExpressionFactory)
ISqlExpressionFactory sqlExpressionFactory,
IRelationalSharedTypeEntityExpansionHelper relationalSharedTypeEntityExpansionHelper)
{
Check.NotNull(relationalSqlTranslatingExpressionVisitorFactory, nameof(relationalSqlTranslatingExpressionVisitorFactory));
Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory));
Check.NotNull(relationalSharedTypeEntityExpansionHelper, nameof(relationalSharedTypeEntityExpansionHelper));

RelationalSqlTranslatingExpressionVisitorFactory = relationalSqlTranslatingExpressionVisitorFactory;
SqlExpressionFactory = sqlExpressionFactory;
RelationalSharedTypeEntityExpansionHelper = relationalSharedTypeEntityExpansionHelper;
}

/// <summary>
Expand All @@ -72,5 +76,11 @@ public RelationalQueryableMethodTranslatingExpressionVisitorDependencies(
/// The SQL expression factory.
/// </summary>
public ISqlExpressionFactory SqlExpressionFactory { get; init; }

/// <summary>
/// Shared type entity expansion helper.
/// </summary>
[EntityFrameworkInternal]
public IRelationalSharedTypeEntityExpansionHelper RelationalSharedTypeEntityExpansionHelper { get; init; }
}
}
Loading

0 comments on commit 77f2d2d

Please sign in to comment.