Skip to content

Commit

Permalink
Query: Implement TPT for relational layer (#21474)
Browse files Browse the repository at this point in the history
Resolves #2266
  • Loading branch information
smitpatel committed Jul 3, 2020
1 parent 98ede46 commit 20d587c
Show file tree
Hide file tree
Showing 27 changed files with 3,096 additions and 636 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Expand Up @@ -679,4 +679,7 @@
<data name="MappedFunctionNotFound" xml:space="preserve">
<value>The entity type '{entityType}' is mapped to the DbFunction named '{functionName}', but no DbFunction with that name was found in the model. Ensure that the entity type mapping is configured using the model name of a function in the model.</value>
</data>
<data name="QueryUnableToIdentifyConcreteTypeInTPT" xml:space="preserve">
<value>Unable to identify the concrete entity type to materialize in TPT hierarchy.</value>
</data>
</root>
115 changes: 59 additions & 56 deletions src/EFCore.Relational/Query/EntityProjectionExpression.cs
Expand Up @@ -23,49 +23,49 @@ namespace Microsoft.EntityFrameworkCore.Query
/// </summary>
public class EntityProjectionExpression : Expression
{
private readonly IDictionary<IProperty, ColumnExpression> _propertyExpressionsCache
= new Dictionary<IProperty, ColumnExpression>();

private readonly IDictionary<INavigation, EntityShaperExpression> _navigationExpressionsCache
private readonly IDictionary<IProperty, ColumnExpression> _propertyExpressionMap = new Dictionary<IProperty, ColumnExpression>();
private readonly IDictionary<INavigation, EntityShaperExpression> _ownedNavigationMap
= new Dictionary<INavigation, EntityShaperExpression>();

private readonly TableExpressionBase _innerTable;
private readonly bool _nullable;

/// <summary>
/// Creates a new instance of the <see cref="EntityProjectionExpression" /> class.
/// </summary>
/// <param name="entityType"> The entity type to shape. </param>
/// <param name="innerTable"> The table from which entity columns are being projected out. </param>
/// <param name="nullable"> A bool value indicating whether this entity instance can be null. </param>
[Obsolete("Use the constructor which takes populated column expressions map.", error: true)]
public EntityProjectionExpression([NotNull] IEntityType entityType, [NotNull] TableExpressionBase innerTable, bool nullable)
{
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(innerTable, nameof(innerTable));

EntityType = entityType;
_innerTable = innerTable;
_nullable = nullable;
throw new NotSupportedException();
}

/// <summary>
/// Creates a new instance of the <see cref="EntityProjectionExpression" /> class.
/// </summary>
/// <param name="entityType"> The entity type to shape. </param>
/// <param name="propertyExpressions"> A dictionary of column expressions corresponding to properties of the entity type. </param>
public EntityProjectionExpression([NotNull] IEntityType entityType, [NotNull] IDictionary<IProperty, ColumnExpression> propertyExpressions)
/// <param name="propertyExpressionMap"> A dictionary of column expressions corresponding to properties of the entity type. </param>
/// <param name="entityTypeIdentifyingExpressionMap"> A dictionary of <see cref="SqlExpression"/> to identify each entity type in hierarchy. </param>
public EntityProjectionExpression(
[NotNull] IEntityType entityType,
[NotNull] IDictionary<IProperty, ColumnExpression> propertyExpressionMap,
[CanBeNull] IReadOnlyDictionary<IEntityType, SqlExpression> entityTypeIdentifyingExpressionMap = null)
{
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(propertyExpressions, nameof(propertyExpressions));
Check.NotNull(propertyExpressionMap, nameof(propertyExpressionMap));

EntityType = entityType;
_propertyExpressionsCache = propertyExpressions;
_propertyExpressionMap = propertyExpressionMap;
EntityTypeIdentifyingExpressionMap = entityTypeIdentifyingExpressionMap;
}

/// <summary>
/// The entity type being projected out.
/// </summary>
public virtual IEntityType EntityType { get; }
/// <summary>
/// Dictionary of entity type identifying expressions.
/// </summary>
public virtual IReadOnlyDictionary<IEntityType, SqlExpression> EntityTypeIdentifyingExpressionMap { get; }
/// <inheritdoc />
public sealed override ExpressionType NodeType => ExpressionType.Extension;
/// <inheritdoc />
Expand All @@ -76,27 +76,31 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
{
Check.NotNull(visitor, nameof(visitor));

if (_innerTable != null)
var changed = false;
var propertyExpressionMap = new Dictionary<IProperty, ColumnExpression>();
foreach (var expression in _propertyExpressionMap)
{
var table = (TableExpressionBase)visitor.Visit(_innerTable);
var newExpression = (ColumnExpression)visitor.Visit(expression.Value);
changed |= newExpression != expression.Value;

return table != _innerTable
? new EntityProjectionExpression(EntityType, table, _nullable)
: this;
propertyExpressionMap[expression.Key] = newExpression;
}

var changed = false;
var newCache = new Dictionary<IProperty, ColumnExpression>();
foreach (var expression in _propertyExpressionsCache)
Dictionary<IEntityType, SqlExpression> entityTypeIdentifyingExpressionMap = null;
if (EntityTypeIdentifyingExpressionMap != null)
{
var newExpression = (ColumnExpression)visitor.Visit(expression.Value);
changed |= newExpression != expression.Value;
entityTypeIdentifyingExpressionMap = new Dictionary<IEntityType, SqlExpression>();
foreach (var expression in EntityTypeIdentifyingExpressionMap)
{
var newExpression = (SqlExpression)visitor.Visit(expression.Value);
changed |= newExpression != expression.Value;

newCache[expression.Key] = newExpression;
entityTypeIdentifyingExpressionMap[expression.Key] = newExpression;
}
}

return changed
? new EntityProjectionExpression(EntityType, newCache)
? new EntityProjectionExpression(EntityType, propertyExpressionMap, entityTypeIdentifyingExpressionMap)
: this;
}

Expand All @@ -106,18 +110,14 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
/// <returns> A new entity projection expression which can project nullable entity. </returns>
public virtual EntityProjectionExpression MakeNullable()
{
if (_innerTable != null)
var propertyExpressionMap = new Dictionary<IProperty, ColumnExpression>();
foreach (var expression in _propertyExpressionMap)
{
return new EntityProjectionExpression(EntityType, _innerTable, nullable: true);
propertyExpressionMap[expression.Key] = expression.Value.MakeNullable();
}

var newCache = new Dictionary<IProperty, ColumnExpression>();
foreach (var expression in _propertyExpressionsCache)
{
newCache[expression.Key] = expression.Value.MakeNullable();
}

return new EntityProjectionExpression(EntityType, newCache);
// We don't need to process EntityTypeIdentifyingExpressionMap because they are already nullable
return new EntityProjectionExpression(EntityType, propertyExpressionMap, EntityTypeIdentifyingExpressionMap);
}

/// <summary>
Expand All @@ -129,23 +129,32 @@ public virtual EntityProjectionExpression UpdateEntityType([NotNull] IEntityType
{
Check.NotNull(derivedType, nameof(derivedType));

if (_innerTable != null)
{
return new EntityProjectionExpression(derivedType, _innerTable, _nullable);
}

var propertyExpressionCache = new Dictionary<IProperty, ColumnExpression>();
foreach (var kvp in _propertyExpressionsCache)
var propertyExpressionMap = new Dictionary<IProperty, ColumnExpression>();
foreach (var kvp in _propertyExpressionMap)
{
var property = kvp.Key;
if (derivedType.IsAssignableFrom(property.DeclaringEntityType)
|| property.DeclaringEntityType.IsAssignableFrom(derivedType))
{
propertyExpressionCache[property] = kvp.Value;
propertyExpressionMap[property] = kvp.Value;
}
}

Dictionary<IEntityType, SqlExpression> entityTypeIdentifyingExpressionMap = null;
if (EntityTypeIdentifyingExpressionMap != null)
{
entityTypeIdentifyingExpressionMap = new Dictionary<IEntityType, SqlExpression>();
foreach (var kvp in EntityTypeIdentifyingExpressionMap)
{
var entityType = kvp.Key;
if (entityType.IsStrictlyDerivedFrom(derivedType))
{
entityTypeIdentifyingExpressionMap[entityType] = kvp.Value;
}
}
}

return new EntityProjectionExpression(derivedType, propertyExpressionCache);
return new EntityProjectionExpression(derivedType, propertyExpressionMap, entityTypeIdentifyingExpressionMap);
}

/// <summary>
Expand All @@ -168,13 +177,7 @@ public virtual ColumnExpression BindProperty([NotNull] IProperty property)
property.Name));
}

if (!_propertyExpressionsCache.TryGetValue(property, out var expression))
{
expression = new ColumnExpression(property, _innerTable, _nullable);
_propertyExpressionsCache[property] = expression;
}

return expression;
return _propertyExpressionMap[property];
}

/// <summary>
Expand All @@ -198,7 +201,7 @@ public virtual void AddNavigationBinding([NotNull] INavigation navigation, [NotN
navigation.Name));
}

_navigationExpressionsCache[navigation] = entityShaper;
_ownedNavigationMap[navigation] = entityShaper;
}

/// <summary>
Expand All @@ -222,7 +225,7 @@ public virtual EntityShaperExpression BindNavigation([NotNull] INavigation navig
navigation.Name));
}

return _navigationExpressionsCache.TryGetValue(navigation, out var expression)
return _ownedNavigationMap.TryGetValue(navigation, out var expression)
? expression
: null;
}
Expand Down
39 changes: 37 additions & 2 deletions src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs
Expand Up @@ -4,10 +4,13 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Query
Expand All @@ -23,6 +26,14 @@ namespace Microsoft.EntityFrameworkCore.Query
/// </summary>
public class RelationalEntityShaperExpression : EntityShaperExpression
{
private static readonly MethodInfo _createUnableToIdentifyConcreteTypeException
= typeof(RelationalEntityShaperExpression).GetTypeInfo()
.GetDeclaredMethod(nameof(CreateUnableToIdentifyConcreteTypeException));

[UsedImplicitly]
private static Exception CreateUnableToIdentifyConcreteTypeException()
=> new InvalidOperationException(RelationalStrings.QueryUnableToIdentifyConcreteTypeInTPT);

/// <summary>
/// Creates a new instance of the <see cref="RelationalEntityShaperExpression" /> class.
/// </summary>
Expand Down Expand Up @@ -55,11 +66,35 @@ protected override LambdaExpression GenerateMaterializationCondition(IEntityType
{
Check.NotNull(entityType, nameof(EntityType));

var baseCondition = base.GenerateMaterializationCondition(entityType, nullable);
LambdaExpression baseCondition;
if (entityType.GetDiscriminatorProperty() == null
&& entityType.GetDirectlyDerivedTypes().Any())
{
// TPT
var valueBufferParameter = Parameter(typeof(ValueBuffer));
var body = entityType.IsAbstract()
? Block(Throw(Call(_createUnableToIdentifyConcreteTypeException)), Constant(null, typeof(IEntityType)))
: (Expression)Constant(entityType, typeof(IEntityType));

var concreteEntityTypes = entityType.GetDerivedTypes().Where(dt => !dt.IsAbstract()).ToArray();
for (var i = 0; i < concreteEntityTypes.Length; i++)
{
body = Condition(
valueBufferParameter.CreateValueBufferReadValueExpression(typeof(bool), i, property: null),
Constant(concreteEntityTypes[i], typeof(IEntityType)),
body);
}

baseCondition = Lambda(body, valueBufferParameter);
}
else
{
baseCondition = base.GenerateMaterializationCondition(entityType, nullable);
}

if (entityType.FindPrimaryKey() != null)
{
var linkingFks = entityType.GetViewOrTableMappings().SingleOrDefault()?.Table.GetRowInternalForeignKeys(entityType);
var linkingFks = entityType.GetViewOrTableMappings().FirstOrDefault()?.Table.GetRowInternalForeignKeys(entityType);
if (linkingFks != null
&& linkingFks.Any())
{
Expand Down

0 comments on commit 20d587c

Please sign in to comment.