Skip to content

Commit

Permalink
Query: Full support for entity splitting
Browse files Browse the repository at this point in the history
Resolves #620
  • Loading branch information
smitpatel committed Sep 12, 2022
1 parent 3a9dd22 commit bb72359
Show file tree
Hide file tree
Showing 15 changed files with 1,057 additions and 508 deletions.
Expand Up @@ -1693,13 +1693,24 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
if (navigation.IsCollection)
{
var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
entityProjectionExpression,
targetEntityType.GetViewOrTableMappings().Single().Table,
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);
TableExpressionBase ownedTable = new TableExpression(targetEntityType.GetViewOrTableMappings().Single().Table);

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

var innerShapedQuery = CreateShapedQueryExpression(
targetEntityType, innerSelectExpression);
var innerSelectExpression = new SelectExpression(targetEntityType, ownedTable);

var innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression);

var makeNullable = foreignKey.PrincipalKey.Properties
.Concat(foreignKey.Properties)
Expand Down Expand Up @@ -1746,102 +1757,9 @@ protected override Expression VisitExtension(Expression extensionExpression)
Expression.Quote(correlationPredicate));
}

var innerShaper = entityProjectionExpression.BindNavigation(navigation);
if (innerShaper == null)
{
// Owned types don't support inheritance See https://github.com/dotnet/efcore/issues/9630
// So there is no handling for dependent having TPT/TPC
// If navigation is defined on derived type and entity type is part of TPT then we need to get ITableBase for derived type.
// TODO: The following code should also handle Function and SqlQuery mappings
var table = navigation.DeclaringEntityType.BaseType == null
|| entityType.FindDiscriminatorProperty() != null
? navigation.DeclaringEntityType.GetViewOrTableMappings().Single().Table
: navigation.DeclaringEntityType.GetViewOrTableMappings().Select(tm => tm.Table)
.Except(navigation.DeclaringEntityType.BaseType.GetViewOrTableMappings().Select(tm => tm.Table))
.Single();
if (table.GetReferencingRowInternalForeignKeys(foreignKey.PrincipalEntityType).Contains(foreignKey))
{
// Mapped to same table
// We get identifying column to figure out tableExpression to pull columns from and nullability of most principal side
var identifyingColumn = entityProjectionExpression.BindProperty(entityType.FindPrimaryKey()!.Properties.First());
var principalNullable = identifyingColumn.IsNullable
// Also make nullable if navigation is on derived type and and principal is TPT
// Since identifying PK would be non-nullable but principal can still be null
// Derived owned navigation does not de-dupe the PK column which for principal is from base table
// and for dependent on derived table
|| (entityType.FindDiscriminatorProperty() == null
&& navigation.DeclaringEntityType.IsStrictlyDerivedFrom(entityShaperExpression.EntityType));

var entityProjection = _selectExpression.GenerateWeakEntityProjectionExpression(
targetEntityType, table, identifyingColumn.Name, identifyingColumn.Table, principalNullable);

if (entityProjection != null)
{
innerShaper = new RelationalEntityShaperExpression(targetEntityType, entityProjection, principalNullable);
}
}

if (innerShaper == null)
{
// InnerShaper is still null if either it is not table sharing or we failed to find table to pick data from
// So we find the table it is mapped to and generate join with it.
// 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 = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
entityProjectionExpression,
table,
navigation);

var innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression);

var makeNullable = foreignKey.PrincipalKey.Properties
.Concat(foreignKey.Properties)
.Select(p => p.ClrType)
.Any(t => t.IsNullableType());

var outerKey = entityShaperExpression.CreateKeyValuesExpression(
navigation.IsOnDependent
? foreignKey.Properties
: foreignKey.PrincipalKey.Properties,
makeNullable);
var innerKey = innerShapedQuery.ShaperExpression.CreateKeyValuesExpression(
navigation.IsOnDependent
? foreignKey.PrincipalKey.Properties
: foreignKey.Properties,
makeNullable);

var joinPredicate = _sqlTranslator.Translate(
Infrastructure.ExpressionExtensions.CreateEqualsExpression(outerKey, innerKey))!;
// Following conditions should match conditions for pushdown on outer during SelectExpression.AddJoin method
var pushdownRequired = _selectExpression.Limit != null
|| _selectExpression.Offset != null
|| _selectExpression.IsDistinct
|| _selectExpression.GroupBy.Count > 0;
_selectExpression.AddLeftJoin(innerSelectExpression, joinPredicate);

// If pushdown was required on SelectExpression then we need to fetch the updated entity projection
if (pushdownRequired)
{
if (doee is not null)
{
entityShaperExpression = _deferredOwnedExpansionRemover.UnwrapDeferredEntityProjectionExpression(doee);
}

entityProjectionExpression = GetEntityProjectionExpression(entityShaperExpression);
}

var leftJoinTable = _selectExpression.Tables.Last();

innerShaper = new RelationalEntityShaperExpression(
targetEntityType,
_selectExpression.GenerateWeakEntityProjectionExpression(
targetEntityType, table, null, leftJoinTable, nullable: true)!,
nullable: true);
}

entityProjectionExpression.AddNavigationBinding(navigation, innerShaper);
}
var innerShaper = entityProjectionExpression.BindNavigation(navigation)
?? _selectExpression.GenerateOwnedReferenceEntityProjectionExpression(
entityProjectionExpression, navigation, _sqlExpressionFactory);
}

return doee is not null
Expand All @@ -1851,29 +1769,6 @@ protected override Expression VisitExtension(Expression extensionExpression)
(ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression,
navigation);

SelectExpression BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
EntityProjectionExpression entityProjectionExpression,
ITableBase targetTable,
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);
TableExpressionBase ownedTable = new TableExpression(targetTable);

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

return _sqlExpressionFactory.Select(targetEntityType, ownedTable);
}

static TableExpressionBase FindRootTableExpressionForColumn(ColumnExpression column)
{
var table = column.Table;
Expand Down

0 comments on commit bb72359

Please sign in to comment.