Skip to content

Commit a31cda4

Browse files
committed
Query: Implement TPT for relational layer
Resolves #2266
1 parent 98ede46 commit a31cda4

27 files changed

+3096
-636
lines changed

src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/EFCore.Relational/Properties/RelationalStrings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,4 +679,7 @@
679679
<data name="MappedFunctionNotFound" xml:space="preserve">
680680
<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>
681681
</data>
682+
<data name="QueryUnableToIdentifyConcreteTypeInTPT" xml:space="preserve">
683+
<value>Unable to identify the concrete entity type to materialize in TPT hierarchy.</value>
684+
</data>
682685
</root>

src/EFCore.Relational/Query/EntityProjectionExpression.cs

Lines changed: 59 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,49 +23,49 @@ namespace Microsoft.EntityFrameworkCore.Query
2323
/// </summary>
2424
public class EntityProjectionExpression : Expression
2525
{
26-
private readonly IDictionary<IProperty, ColumnExpression> _propertyExpressionsCache
27-
= new Dictionary<IProperty, ColumnExpression>();
28-
29-
private readonly IDictionary<INavigation, EntityShaperExpression> _navigationExpressionsCache
26+
private readonly IDictionary<IProperty, ColumnExpression> _propertyExpressionMap = new Dictionary<IProperty, ColumnExpression>();
27+
private readonly IDictionary<INavigation, EntityShaperExpression> _ownedNavigationMap
3028
= new Dictionary<INavigation, EntityShaperExpression>();
3129

32-
private readonly TableExpressionBase _innerTable;
33-
private readonly bool _nullable;
34-
3530
/// <summary>
3631
/// Creates a new instance of the <see cref="EntityProjectionExpression" /> class.
3732
/// </summary>
3833
/// <param name="entityType"> The entity type to shape. </param>
3934
/// <param name="innerTable"> The table from which entity columns are being projected out. </param>
4035
/// <param name="nullable"> A bool value indicating whether this entity instance can be null. </param>
36+
[Obsolete("Use the constructor which takes populated column expressions map.", error: true)]
4137
public EntityProjectionExpression([NotNull] IEntityType entityType, [NotNull] TableExpressionBase innerTable, bool nullable)
4238
{
43-
Check.NotNull(entityType, nameof(entityType));
44-
Check.NotNull(innerTable, nameof(innerTable));
45-
46-
EntityType = entityType;
47-
_innerTable = innerTable;
48-
_nullable = nullable;
39+
throw new NotSupportedException();
4940
}
5041

5142
/// <summary>
5243
/// Creates a new instance of the <see cref="EntityProjectionExpression" /> class.
5344
/// </summary>
5445
/// <param name="entityType"> The entity type to shape. </param>
55-
/// <param name="propertyExpressions"> A dictionary of column expressions corresponding to properties of the entity type. </param>
56-
public EntityProjectionExpression([NotNull] IEntityType entityType, [NotNull] IDictionary<IProperty, ColumnExpression> propertyExpressions)
46+
/// <param name="propertyExpressionMap"> A dictionary of column expressions corresponding to properties of the entity type. </param>
47+
/// <param name="entityTypeIdentifyingExpressionMap"> A dictionary of <see cref="SqlExpression"/> to identify each entity type in hierarchy. </param>
48+
public EntityProjectionExpression(
49+
[NotNull] IEntityType entityType,
50+
[NotNull] IDictionary<IProperty, ColumnExpression> propertyExpressionMap,
51+
[CanBeNull] IReadOnlyDictionary<IEntityType, SqlExpression> entityTypeIdentifyingExpressionMap = null)
5752
{
5853
Check.NotNull(entityType, nameof(entityType));
59-
Check.NotNull(propertyExpressions, nameof(propertyExpressions));
54+
Check.NotNull(propertyExpressionMap, nameof(propertyExpressionMap));
6055

6156
EntityType = entityType;
62-
_propertyExpressionsCache = propertyExpressions;
57+
_propertyExpressionMap = propertyExpressionMap;
58+
EntityTypeIdentifyingExpressionMap = entityTypeIdentifyingExpressionMap;
6359
}
6460

6561
/// <summary>
6662
/// The entity type being projected out.
6763
/// </summary>
6864
public virtual IEntityType EntityType { get; }
65+
/// <summary>
66+
/// Dictionary of entity type identifying expressions.
67+
/// </summary>
68+
public virtual IReadOnlyDictionary<IEntityType, SqlExpression> EntityTypeIdentifyingExpressionMap { get; }
6969
/// <inheritdoc />
7070
public sealed override ExpressionType NodeType => ExpressionType.Extension;
7171
/// <inheritdoc />
@@ -76,27 +76,31 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
7676
{
7777
Check.NotNull(visitor, nameof(visitor));
7878

79-
if (_innerTable != null)
79+
var changed = false;
80+
var propertyExpressionMap = new Dictionary<IProperty, ColumnExpression>();
81+
foreach (var expression in _propertyExpressionMap)
8082
{
81-
var table = (TableExpressionBase)visitor.Visit(_innerTable);
83+
var newExpression = (ColumnExpression)visitor.Visit(expression.Value);
84+
changed |= newExpression != expression.Value;
8285

83-
return table != _innerTable
84-
? new EntityProjectionExpression(EntityType, table, _nullable)
85-
: this;
86+
propertyExpressionMap[expression.Key] = newExpression;
8687
}
8788

88-
var changed = false;
89-
var newCache = new Dictionary<IProperty, ColumnExpression>();
90-
foreach (var expression in _propertyExpressionsCache)
89+
Dictionary<IEntityType, SqlExpression> entityTypeIdentifyingExpressionMap = null;
90+
if (EntityTypeIdentifyingExpressionMap != null)
9191
{
92-
var newExpression = (ColumnExpression)visitor.Visit(expression.Value);
93-
changed |= newExpression != expression.Value;
92+
entityTypeIdentifyingExpressionMap = new Dictionary<IEntityType, SqlExpression>();
93+
foreach (var expression in EntityTypeIdentifyingExpressionMap)
94+
{
95+
var newExpression = (SqlExpression)visitor.Visit(expression.Value);
96+
changed |= newExpression != expression.Value;
9497

95-
newCache[expression.Key] = newExpression;
98+
entityTypeIdentifyingExpressionMap[expression.Key] = newExpression;
99+
}
96100
}
97101

98102
return changed
99-
? new EntityProjectionExpression(EntityType, newCache)
103+
? new EntityProjectionExpression(EntityType, propertyExpressionMap, entityTypeIdentifyingExpressionMap)
100104
: this;
101105
}
102106

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

114-
var newCache = new Dictionary<IProperty, ColumnExpression>();
115-
foreach (var expression in _propertyExpressionsCache)
116-
{
117-
newCache[expression.Key] = expression.Value.MakeNullable();
118-
}
119-
120-
return new EntityProjectionExpression(EntityType, newCache);
119+
// We don't need to process EntityTypeIdentifyingExpressionMap because they are already nullable
120+
return new EntityProjectionExpression(EntityType, propertyExpressionMap, EntityTypeIdentifyingExpressionMap);
121121
}
122122

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

132-
if (_innerTable != null)
133-
{
134-
return new EntityProjectionExpression(derivedType, _innerTable, _nullable);
135-
}
136-
137-
var propertyExpressionCache = new Dictionary<IProperty, ColumnExpression>();
138-
foreach (var kvp in _propertyExpressionsCache)
132+
var propertyExpressionMap = new Dictionary<IProperty, ColumnExpression>();
133+
foreach (var kvp in _propertyExpressionMap)
139134
{
140135
var property = kvp.Key;
141136
if (derivedType.IsAssignableFrom(property.DeclaringEntityType)
142137
|| property.DeclaringEntityType.IsAssignableFrom(derivedType))
143138
{
144-
propertyExpressionCache[property] = kvp.Value;
139+
propertyExpressionMap[property] = kvp.Value;
140+
}
141+
}
142+
143+
Dictionary<IEntityType, SqlExpression> entityTypeIdentifyingExpressionMap = null;
144+
if (EntityTypeIdentifyingExpressionMap != null)
145+
{
146+
entityTypeIdentifyingExpressionMap = new Dictionary<IEntityType, SqlExpression>();
147+
foreach (var kvp in EntityTypeIdentifyingExpressionMap)
148+
{
149+
var entityType = kvp.Key;
150+
if (entityType.IsStrictlyDerivedFrom(derivedType))
151+
{
152+
entityTypeIdentifyingExpressionMap[entityType] = kvp.Value;
153+
}
145154
}
146155
}
147156

148-
return new EntityProjectionExpression(derivedType, propertyExpressionCache);
157+
return new EntityProjectionExpression(derivedType, propertyExpressionMap, entityTypeIdentifyingExpressionMap);
149158
}
150159

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

171-
if (!_propertyExpressionsCache.TryGetValue(property, out var expression))
172-
{
173-
expression = new ColumnExpression(property, _innerTable, _nullable);
174-
_propertyExpressionsCache[property] = expression;
175-
}
176-
177-
return expression;
180+
return _propertyExpressionMap[property];
178181
}
179182

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

201-
_navigationExpressionsCache[navigation] = entityShaper;
204+
_ownedNavigationMap[navigation] = entityShaper;
202205
}
203206

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

225-
return _navigationExpressionsCache.TryGetValue(navigation, out var expression)
228+
return _ownedNavigationMap.TryGetValue(navigation, out var expression)
226229
? expression
227230
: null;
228231
}

src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
using System;
55
using System.Linq;
66
using System.Linq.Expressions;
7+
using System.Reflection;
78
using JetBrains.Annotations;
9+
using Microsoft.EntityFrameworkCore.Diagnostics;
810
using Microsoft.EntityFrameworkCore.Infrastructure;
911
using Microsoft.EntityFrameworkCore.Metadata;
1012
using Microsoft.EntityFrameworkCore.Metadata.Internal;
13+
using Microsoft.EntityFrameworkCore.Storage;
1114
using Microsoft.EntityFrameworkCore.Utilities;
1215

1316
namespace Microsoft.EntityFrameworkCore.Query
@@ -23,6 +26,14 @@ namespace Microsoft.EntityFrameworkCore.Query
2326
/// </summary>
2427
public class RelationalEntityShaperExpression : EntityShaperExpression
2528
{
29+
private static readonly MethodInfo _createUnableToIdentifyConcreteTypeException
30+
= typeof(RelationalEntityShaperExpression).GetTypeInfo()
31+
.GetDeclaredMethod(nameof(CreateUnableToIdentifyConcreteTypeException));
32+
33+
[UsedImplicitly]
34+
private static Exception CreateUnableToIdentifyConcreteTypeException()
35+
=> new InvalidOperationException(RelationalStrings.QueryUnableToIdentifyConcreteTypeInTPT);
36+
2637
/// <summary>
2738
/// Creates a new instance of the <see cref="RelationalEntityShaperExpression" /> class.
2839
/// </summary>
@@ -55,11 +66,35 @@ protected override LambdaExpression GenerateMaterializationCondition(IEntityType
5566
{
5667
Check.NotNull(entityType, nameof(EntityType));
5768

58-
var baseCondition = base.GenerateMaterializationCondition(entityType, nullable);
69+
LambdaExpression baseCondition;
70+
if (entityType.GetDiscriminatorProperty() == null
71+
&& entityType.GetDirectlyDerivedTypes().Any())
72+
{
73+
// TPT
74+
var valueBufferParameter = Parameter(typeof(ValueBuffer));
75+
var body = entityType.IsAbstract()
76+
? Block(Throw(Call(_createUnableToIdentifyConcreteTypeException)), Constant(null, typeof(IEntityType)))
77+
: (Expression)Constant(entityType, typeof(IEntityType));
78+
79+
var concreteEntityTypes = entityType.GetDerivedTypes().Where(dt => !dt.IsAbstract()).ToArray();
80+
for (var i = 0; i < concreteEntityTypes.Length; i++)
81+
{
82+
body = Condition(
83+
valueBufferParameter.CreateValueBufferReadValueExpression(typeof(bool), i, property: null),
84+
Constant(concreteEntityTypes[i], typeof(IEntityType)),
85+
body);
86+
}
87+
88+
baseCondition = Lambda(body, valueBufferParameter);
89+
}
90+
else
91+
{
92+
baseCondition = base.GenerateMaterializationCondition(entityType, nullable);
93+
}
5994

6095
if (entityType.FindPrimaryKey() != null)
6196
{
62-
var linkingFks = entityType.GetViewOrTableMappings().SingleOrDefault()?.Table.GetRowInternalForeignKeys(entityType);
97+
var linkingFks = entityType.GetViewOrTableMappings().FirstOrDefault()?.Table.GetRowInternalForeignKeys(entityType);
6398
if (linkingFks != null
6499
&& linkingFks.Any())
65100
{

0 commit comments

Comments
 (0)