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

Query: Optimize usage of GetViewOrTableMappings #28019

Merged
1 commit merged into from
May 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 @@ -46,9 +46,8 @@ public static IReadOnlyList<string> GetTptDiscriminatorValues(this IReadOnlyEnti
/// 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 static IReadOnlyList<IProperty> GetNonPrincipalSharedNonPkProperties(this IEntityType entityType, ITableBase table)
public static IEnumerable<IProperty> GetNonPrincipalSharedNonPkProperties(this IEntityType entityType, ITableBase table)
{
var nonPrincipalSharedProperties = new List<IProperty>();
var principalEntityTypes = new HashSet<IEntityType>();
PopulatePrincipalEntityTypes(table, entityType, principalEntityTypes);
foreach (var property in entityType.GetProperties())
Expand All @@ -65,11 +64,9 @@ public static IReadOnlyList<IProperty> GetNonPrincipalSharedNonPkProperties(this
continue;
}

nonPrincipalSharedProperties.Add(property);
yield return property;
}

return nonPrincipalSharedProperties;

static void PopulatePrincipalEntityTypes(ITableBase table, IEntityType entityType, HashSet<IEntityType> entityTypes)
{
foreach (var linkingFk in table.GetRowInternalForeignKeys(entityType))
Expand Down
70 changes: 25 additions & 45 deletions src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ protected RelationalEntityShaperExpression(
protected override LambdaExpression GenerateMaterializationCondition(IEntityType entityType, bool nullable)
{
LambdaExpression baseCondition;
if (entityType.FindDiscriminatorProperty() == null
// Generate discriminator condition
var containsDiscriminatorProperty = entityType.FindDiscriminatorProperty() != null;
if (!containsDiscriminatorProperty
&& entityType.GetDirectlyDerivedTypes().Any())
{
// TPT
// TPT/TPC
var valueBufferParameter = Parameter(typeof(ValueBuffer));
var discriminatorValueVariable = Variable(typeof(string), "discriminator");
var expressions = new List<Expression>
Expand Down Expand Up @@ -84,51 +86,29 @@ protected override LambdaExpression GenerateMaterializationCondition(IEntityType
baseCondition = base.GenerateMaterializationCondition(entityType, nullable);
}

if (entityType.FindPrimaryKey() != null)
if (containsDiscriminatorProperty
|| entityType.FindPrimaryKey() == null
|| entityType.GetRootType() != entityType
|| entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy)
{
var table = entityType.GetViewOrTableMappings().FirstOrDefault()?.Table;
if (table != null
&& table.IsOptional(entityType))
{
// Optional dependent
var body = baseCondition.Body;
var valueBufferParameter = baseCondition.Parameters[0];
Expression? condition = null;
var requiredNonPkProperties = entityType.GetProperties().Where(p => !p.IsNullable && !p.IsPrimaryKey()).ToList();
if (requiredNonPkProperties.Count > 0)
{
condition = requiredNonPkProperties
.Select(
p => NotEqual(
valueBufferParameter.CreateValueBufferReadValueExpression(typeof(object), p.GetIndex(), p),
Constant(null)))
.Aggregate((a, b) => AndAlso(a, b));
}

var allNonPrincipalSharedNonPkProperties = entityType.GetNonPrincipalSharedNonPkProperties(table);
// We don't need condition for nullable property if there exist at least one required property which is non shared.
if (allNonPrincipalSharedNonPkProperties.Count != 0
&& allNonPrincipalSharedNonPkProperties.All(p => p.IsNullable))
{
var atLeastOneNonNullValueInNullablePropertyCondition = allNonPrincipalSharedNonPkProperties
.Select(
p => NotEqual(
valueBufferParameter.CreateValueBufferReadValueExpression(typeof(object), p.GetIndex(), p),
Constant(null)))
.Aggregate((a, b) => OrElse(a, b));

condition = condition == null
? atLeastOneNonNullValueInNullablePropertyCondition
: AndAlso(condition, atLeastOneNonNullValueInNullablePropertyCondition);
}

if (condition != null)
{
body = Condition(condition, body, Default(typeof(IEntityType)));
}
return baseCondition;
}

return Lambda(body, valueBufferParameter);
}
var table = entityType.GetViewOrTableMappings().SingleOrDefault()?.Table
?? entityType.GetDefaultMappings().Single().Table;
if (table.IsOptional(entityType))
{
// Optional dependent
var valueBufferParameter = baseCondition.Parameters[0];
var condition = entityType.GetNonPrincipalSharedNonPkProperties(table)
.Where(e => !e.IsNullable)
.Select(
p => NotEqual(
valueBufferParameter.CreateValueBufferReadValueExpression(typeof(object), p.GetIndex(), p),
Constant(null)))
.Aggregate((a, b) => AndAlso(a, b));

return Lambda(Condition(condition, baseCondition.Body, Default(typeof(IEntityType))), valueBufferParameter);
}

return baseCondition;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
entityProjectionExpression,
targetEntityType.GetViewOrTableMappings().Single().Table,
navigation);

var innerShapedQuery = CreateShapedQueryExpression(
Expand Down Expand Up @@ -1137,7 +1138,7 @@ outerKey is NewArrayExpression newArrayExpression
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
// 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
Expand Down Expand Up @@ -1177,6 +1178,7 @@ outerKey is NewArrayExpression newArrayExpression
table = targetEntityType.GetViewOrTableMappings().Single().Table;
var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
entityProjectionExpression,
table,
navigation);

var innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression);
Expand Down Expand Up @@ -1237,6 +1239,7 @@ outerKey is NewArrayExpression newArrayExpression

SelectExpression BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
EntityProjectionExpression entityProjectionExpression,
ITableBase targetTable,
INavigation navigation)
{
// just need any column - we use it only to extract the table it originated from
Expand All @@ -1247,7 +1250,7 @@ SelectExpression BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable(
: foreignKey.PrincipalKey.Properties[0]);

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

foreach (var annotation in sourceTable.GetAnnotations())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1093,51 +1093,35 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression)
var propertyAccess = entityProjectionExpression.BindProperty(property);

var entityType = entityReferenceExpression.EntityType;
var table = entityType.GetViewOrTableMappings().FirstOrDefault()?.Table;
if ((table?.IsOptional(entityType)) != true)
if (entityType.FindDiscriminatorProperty() != null
|| entityType.FindPrimaryKey() == null
|| entityType.GetRootType() != entityType
|| entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy)
{
return propertyAccess;
}

// this is optional dependent sharing table
var nonPrincipalSharedNonPkProperties = entityType.GetNonPrincipalSharedNonPkProperties(table);
if (nonPrincipalSharedNonPkProperties.Contains(property))
var table = entityType.GetViewOrTableMappings().SingleOrDefault()?.Table
?? entityType.GetDefaultMappings().Single().Table;
if (!table.IsOptional(entityType))
{
// The column is not being shared with principal side so we can always use directly
return propertyAccess;
}

SqlExpression? condition = null;
// Property is being shared with principal side, so we need to make it conditional access
var allRequiredNonPkPropertiesCondition =
entityType.GetProperties().Where(p => !p.IsNullable && !p.IsPrimaryKey()).ToList();
if (allRequiredNonPkPropertiesCondition.Count > 0)
{
condition = allRequiredNonPkPropertiesCondition.Select(p => entityProjectionExpression.BindProperty(p))
.Select(c => (SqlExpression)_sqlExpressionFactory.NotEqual(c, _sqlExpressionFactory.Constant(null)))
.Aggregate((a, b) => _sqlExpressionFactory.AndAlso(a, b));
}

if (nonPrincipalSharedNonPkProperties.Count != 0
&& nonPrincipalSharedNonPkProperties.All(p => p.IsNullable))
{
// If all non principal shared properties are nullable then we need additional condition
var atLeastOneNonNullValueInNullableColumnsCondition = nonPrincipalSharedNonPkProperties
.Select(p => entityProjectionExpression.BindProperty(p))
.Select(c => (SqlExpression)_sqlExpressionFactory.NotEqual(c, _sqlExpressionFactory.Constant(null)))
.Aggregate((a, b) => _sqlExpressionFactory.OrElse(a, b));

condition = condition == null
? atLeastOneNonNullValueInNullableColumnsCondition
: _sqlExpressionFactory.AndAlso(condition, atLeastOneNonNullValueInNullableColumnsCondition);
}

if (condition == null)
// this is optional dependent sharing table
var nonPrincipalSharedNonPkProperties = entityType.GetNonPrincipalSharedNonPkProperties(table).ToList();
if (nonPrincipalSharedNonPkProperties.Contains(property))
{
// if we cannot compute condition then we just return property access (and hope for the best)
// The column is not being shared with principal side so we can always use directly
return propertyAccess;
}

var condition = nonPrincipalSharedNonPkProperties
.Where(e => !e.IsNullable)
.Select(p => entityProjectionExpression.BindProperty(p))
.Select(c => (SqlExpression)_sqlExpressionFactory.NotEqual(c, _sqlExpressionFactory.Constant(null)))
.Aggregate((a, b) => _sqlExpressionFactory.AndAlso(a, b));

return _sqlExpressionFactory.Case(
new List<CaseWhenClause> { new(condition, propertyAccess) },
elseResult: null);
Expand Down Expand Up @@ -1192,7 +1176,7 @@ private bool ProcessOrderByThenBy(
{
enumerableExpression.ApplyOrdering(orderingExpression);
}

return true;
}

Expand Down Expand Up @@ -1406,36 +1390,29 @@ private bool TryRewriteEntityEquality(
|| IsNullSqlConstantExpression(right))
{
var nonNullEntityReference = (IsNullSqlConstantExpression(left) ? rightEntityReference : leftEntityReference)!;
var entityType1 = nonNullEntityReference.EntityType;
var table = entityType1.GetViewOrTableMappings().FirstOrDefault()?.Table;
if (table?.IsOptional(entityType1) == true)
var nullComparedEntityType = nonNullEntityReference.EntityType;
var nullComparedEntityTypePrimaryKeyProperties = nullComparedEntityType.FindPrimaryKey()?.Properties;
if (nullComparedEntityTypePrimaryKeyProperties == null)
{
Expression? condition = null;
// Optional dependent sharing table
var requiredNonPkProperties = entityType1.GetProperties().Where(p => !p.IsNullable && !p.IsPrimaryKey()).ToList();
if (requiredNonPkProperties.Count > 0)
{
condition = requiredNonPkProperties.Select(
p =>
{
var comparison = Expression.Call(
ObjectEqualsMethodInfo,
Expression.Convert(CreatePropertyAccessExpression(nonNullEntityReference, p), typeof(object)),
Expression.Convert(Expression.Constant(null, p.ClrType.MakeNullable()), typeof(object)));

return nodeType == ExpressionType.Equal
? (Expression)comparison
: Expression.Not(comparison);
})
.Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.OrElse(l, r) : Expression.AndAlso(l, r));
}
throw new InvalidOperationException(
CoreStrings.EntityEqualityOnKeylessEntityNotSupported(
nodeType == ExpressionType.Equal
? equalsMethod ? nameof(object.Equals) : "=="
: equalsMethod
? "!" + nameof(object.Equals)
: "!=",
nullComparedEntityType.DisplayName()));
}

var allNonPrincipalSharedNonPkProperties = entityType1.GetNonPrincipalSharedNonPkProperties(table);
// We don't need condition for nullable property if there exist at least one required property which is non shared.
if (allNonPrincipalSharedNonPkProperties.Count != 0
&& allNonPrincipalSharedNonPkProperties.All(p => p.IsNullable))
if (nullComparedEntityType.GetRootType() == nullComparedEntityType
&& nullComparedEntityType.GetMappingStrategy() != RelationalAnnotationNames.TpcMappingStrategy)
{
var table = nullComparedEntityType.GetViewOrTableMappings().SingleOrDefault()?.Table
?? nullComparedEntityType.GetDefaultMappings().Single().Table;
if (table.IsOptional(nullComparedEntityType))
{
var atLeastOneNonNullValueInNullablePropertyCondition = allNonPrincipalSharedNonPkProperties
var condition = nullComparedEntityType.GetNonPrincipalSharedNonPkProperties(table)
.Where(e => !e.IsNullable)
.Select(
p =>
{
Expand All @@ -1448,40 +1425,15 @@ private bool TryRewriteEntityEquality(
? (Expression)comparison
: Expression.Not(comparison);
})
.Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.AndAlso(l, r) : Expression.OrElse(l, r));

condition = condition == null
? atLeastOneNonNullValueInNullablePropertyCondition
: nodeType == ExpressionType.Equal
? Expression.OrElse(condition, atLeastOneNonNullValueInNullablePropertyCondition)
: Expression.AndAlso(condition, atLeastOneNonNullValueInNullablePropertyCondition);
}
.Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.OrElse(l, r) : Expression.AndAlso(l, r));

if (condition != null)
{
result = Visit(condition);
return true;
}

result = null;
return false;
}

var primaryKeyProperties1 = entityType1.FindPrimaryKey()?.Properties;
if (primaryKeyProperties1 == null)
{
throw new InvalidOperationException(
CoreStrings.EntityEqualityOnKeylessEntityNotSupported(
nodeType == ExpressionType.Equal
? equalsMethod ? nameof(object.Equals) : "=="
: equalsMethod
? "!" + nameof(object.Equals)
: "!=",
entityType1.DisplayName()));
}

result = Visit(
primaryKeyProperties1.Select(
nullComparedEntityTypePrimaryKeyProperties.Select(
p =>
{
var comparison = Expression.Call(
Expand Down Expand Up @@ -1702,19 +1654,19 @@ public Expression Convert(Type type)
}

private sealed class SqlTypeMappingVerifyingExpressionVisitor : ExpressionVisitor
{
protected override Expression VisitExtension(Expression extensionExpression)
{
if (extensionExpression is SqlExpression sqlExpression
&& extensionExpression is not SqlFragmentExpression)
protected override Expression VisitExtension(Expression extensionExpression)
{
if (sqlExpression.TypeMapping == null)
if (extensionExpression is SqlExpression sqlExpression
&& extensionExpression is not SqlFragmentExpression)
{
throw new InvalidOperationException(RelationalStrings.NullTypeMappingInSqlTree(sqlExpression.Print()));
if (sqlExpression.TypeMapping == null)
{
throw new InvalidOperationException(RelationalStrings.NullTypeMappingInSqlTree(sqlExpression.Print()));
}
}
}

return base.VisitExtension(extensionExpression);
return base.VisitExtension(extensionExpression);
}
}
}
}
Loading