-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Adding support for owned types mapped to same table as well as different tables #26706
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using Microsoft.EntityFrameworkCore.Metadata; | ||
using Microsoft.EntityFrameworkCore.Query.SqlExpressions; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace Microsoft.EntityFrameworkCore.Query | ||
{ | ||
/// <summary> | ||
/// Service which helps with various aspects of shared type entity expansion extensibility for relational providrers. | ||
/// </summary> | ||
/// <para> | ||
/// The service lifetime is <see cref="ServiceLifetime.Scoped" />. This means that each | ||
/// <see cref="DbContext" /> instance will use its own instance of this service. | ||
/// The implementation may depend on other services registered with any lifetime. | ||
/// The implementation does not need to be thread-safe. | ||
/// </para> | ||
/// <remarks> | ||
/// See <see href="https://aka.ms/efcore-docs-providers">Implementation of database providers and extensions</see> | ||
/// and <see href="https://aka.ms/efcore-how-queries-work">How EF Core queries work</see> for more information. | ||
/// </remarks> | ||
public interface IRelationalSharedTypeEntityExpansionHelper | ||
{ | ||
/// <summary> | ||
/// Creates a SelectExpression representing owned type. | ||
/// </summary> | ||
public SelectExpression CreateInnerSelectExpression( | ||
TableExpressionBase sourceTable, | ||
IEntityType targetEntityType); | ||
|
||
/// <summary> | ||
/// Returns true if the given table expression matches table metadata, false otherwise. | ||
/// </summary> | ||
public bool TableMatchesMetadata( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The moment we needed to add this, we faulted our own design. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, ideally there would be an abstract class for table-like sources (like "tableExpressionBase" and what currently is teb should be named something else?) from which TableExpression and temporalTable expression would inherit. Alternatively TemporalTableExpression should inherit from TableExpression rather than TableExpressionBase, but thats a smell also, since TemporalTableExpression is abstract and TableExpression is not :( |
||
TableExpressionBase tableExpression, | ||
ITableBase tableMetadata); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,7 +46,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; | ||
|
@@ -70,7 +73,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; | ||
|
@@ -997,15 +1004,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!; | ||
} | ||
|
||
|
@@ -1087,11 +1097,21 @@ protected override Expression VisitExtension(Expression extensionExpression) | |
return null; | ||
} | ||
|
||
var entityProjectionExpression = (EntityProjectionExpression) | ||
(entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression | ||
? _selectExpression.GetProjection(projectionBindingExpression) | ||
: entityShaperExpression.ValueBufferExpression); | ||
|
||
var foreignKey = navigation.ForeignKey; | ||
if (navigation.IsCollection) | ||
{ | ||
var innerShapedQuery = CreateShapedQueryExpression( | ||
targetEntityType, _sqlExpressionFactory.Select(targetEntityType)); | ||
var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @smitpatel some refactoring here compared to 6.0 version, which didn't contain the logic for collection (which is also mapped to different table, the logic is pretty much the same) so I DRY'd it a bit |
||
entityProjectionExpression, | ||
navigation, | ||
foreignKey, | ||
targetEntityType); | ||
|
||
var innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression); | ||
|
||
var makeNullable = foreignKey.PrincipalKey.Properties | ||
.Concat(foreignKey.Properties) | ||
|
@@ -1139,11 +1159,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) | ||
{ | ||
|
@@ -1171,7 +1186,12 @@ outerKey is NewArrayExpression newArrayExpression | |
&& navigation.DeclaringEntityType.IsStrictlyDerivedFrom(entityShaperExpression.EntityType)); | ||
|
||
var entityProjection = _selectExpression.GenerateWeakEntityProjectionExpression( | ||
targetEntityType, table, identifyingColumn.Name, identifyingColumn.Table, principalNullable); | ||
targetEntityType, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are no weak entity types anymore |
||
table, | ||
identifyingColumn.Name, | ||
identifyingColumn.Table, | ||
_sharedTypeEntityExpansionHelper, | ||
principalNullable); | ||
|
||
if (entityProjection != null) | ||
{ | ||
|
@@ -1186,7 +1206,13 @@ 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, | ||
foreignKey, | ||
targetEntityType); | ||
|
||
var innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression); | ||
|
||
var makeNullable = foreignKey.PrincipalKey.Properties | ||
|
@@ -1212,14 +1238,60 @@ outerKey is NewArrayExpression newArrayExpression | |
innerShaper = new RelationalEntityShaperExpression( | ||
targetEntityType, | ||
_selectExpression.GenerateWeakEntityProjectionExpression( | ||
targetEntityType, table, null, leftJoinTable, nullable: true)!, | ||
targetEntityType, | ||
table, | ||
null, | ||
leftJoinTable, | ||
_sharedTypeEntityExpansionHelper, | ||
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); | ||
|
||
return _sharedTypeEntityExpansionHelper.CreateInnerSelectExpression( | ||
sourceTable, | ||
targetEntityType); | ||
} | ||
|
||
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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// 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 | ||
{ | ||
/// <inheritdoc/> | ||
public class RelationalSharedTypeEntityExpansionHelper : IRelationalSharedTypeEntityExpansionHelper | ||
{ | ||
/// <summary> | ||
/// Creates a new instance of the <see cref="RelationalSharedTypeEntityExpansionHelper" /> class. | ||
/// </summary> | ||
/// <param name="dependencies">Dependencies for this service.</param> | ||
public RelationalSharedTypeEntityExpansionHelper(RelationalSharedTypeEntityExpansionHelperDependencies dependencies) | ||
{ | ||
Dependencies = dependencies; | ||
} | ||
|
||
/// <summary> | ||
/// Dependencies for this service. | ||
/// </summary> | ||
protected virtual RelationalSharedTypeEntityExpansionHelperDependencies Dependencies { get; } | ||
|
||
/// <inheritdoc/> | ||
public virtual SelectExpression CreateInnerSelectExpression( | ||
TableExpressionBase sourceTable, | ||
IEntityType targetEntityType) | ||
=> Dependencies.SqlExpressionFactory.Select(targetEntityType); | ||
|
||
/// <inheritdoc/> | ||
public virtual bool TableMatchesMetadata(TableExpressionBase tableExpression, ITableBase tableMetadata) | ||
=> tableExpression is TableExpression table | ||
&& table.Name == tableMetadata.Name | ||
&& table.Schema == tableMetadata.Schema; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.