Skip to content

Commit 241d93e

Browse files
committed
Query: Add support for TPC
Part of #3170
1 parent 3ea4803 commit 241d93e

24 files changed

+11912
-1789
lines changed

src/EFCore.Relational/Query/EntityProjectionExpression.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,13 @@ public virtual EntityProjectionExpression MakeNullable()
8787
propertyExpressionMap[property] = columnExpression.MakeNullable();
8888
}
8989

90-
// We don't need to process DiscriminatorExpression because they are already nullable
91-
return new EntityProjectionExpression(EntityType, propertyExpressionMap, DiscriminatorExpression);
90+
var discriminatorExpression = DiscriminatorExpression;
91+
if (discriminatorExpression is ColumnExpression ce)
92+
{
93+
// if discriminator is column then we need to make it nullable
94+
discriminatorExpression = ce.MakeNullable();
95+
}
96+
return new EntityProjectionExpression(EntityType, propertyExpressionMap, discriminatorExpression);
9297
}
9398

9499
/// <summary>

src/EFCore.Relational/Query/ISqlExpressionFactory.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -435,9 +435,18 @@ SqlFunctionExpression NiladicFunction(
435435
/// </summary>
436436
/// <param name="value">A value.</param>
437437
/// <param name="typeMapping">The <see cref="RelationalTypeMapping" /> associated with the expression.</param>
438-
/// <returns>An expression representing a LIKE in a SQL tree.</returns>
438+
/// <returns>An expression representing a constant in a SQL tree.</returns>
439439
SqlConstantExpression Constant(object? value, RelationalTypeMapping? typeMapping = null);
440440

441+
/// <summary>
442+
/// Creates a new <see cref="SqlConstantExpression" /> which represents a constant in a SQL tree.
443+
/// </summary>
444+
/// <param name="value">A value.</param>
445+
/// <param name="type">The type for the constant. Useful when value is null.</param>
446+
/// <param name="typeMapping">The <see cref="RelationalTypeMapping" /> associated with the expression.</param>
447+
/// <returns>An expression representing a constant in a SQL tree.</returns>
448+
SqlConstantExpression Constant(object? value, Type type, RelationalTypeMapping? typeMapping = null);
449+
441450
/// <summary>
442451
/// Creates a new <see cref="SqlFragmentExpression" /> which represents a SQL token.
443452
/// </summary>

src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs

+41-8
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,10 @@ private static readonly MethodInfo CollectionAccessorAddMethodInfo
113113
private readonly IDictionary<ParameterExpression, IDictionary<IProperty, int>> _materializationContextBindings
114114
= new Dictionary<ParameterExpression, IDictionary<IProperty, int>>();
115115

116-
private readonly IDictionary<ParameterExpression, int> _entityTypeIdentifyingExpressionOffsets
117-
= new Dictionary<ParameterExpression, int>();
116+
private readonly IDictionary<ParameterExpression, object> _entityTypeIdentifyingExpressionInfo
117+
= new Dictionary<ParameterExpression, object>();
118+
private readonly IDictionary<ProjectionBindingExpression, string> _singleEntityTypeDiscriminatorValues
119+
= new Dictionary<ProjectionBindingExpression, string>();
118120

119121
public ShaperProcessingExpressionVisitor(
120122
RelationalShapedQueryCompilingExpressionVisitor parentVisitor,
@@ -359,7 +361,13 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
359361

360362
var propertyMap = (IDictionary<IProperty, int>)GetProjectionIndex(projectionBindingExpression);
361363
_materializationContextBindings[parameterExpression] = propertyMap;
362-
_entityTypeIdentifyingExpressionOffsets[parameterExpression] = propertyMap.Values.Max() + 1;
364+
_entityTypeIdentifyingExpressionInfo[parameterExpression] =
365+
// If single entity type is being selected in hierarchy then we use the value directly else we store the offset to
366+
// read discriminator value.
367+
_singleEntityTypeDiscriminatorValues.TryGetValue(projectionBindingExpression, out var value)
368+
? value
369+
: propertyMap.Values.Max() + 1;
370+
363371

364372
var updatedExpression = newExpression.Update(
365373
new[] { Expression.Constant(ValueBuffer.Empty), newExpression.Arguments[1] });
@@ -388,6 +396,16 @@ protected override Expression VisitExtension(Expression extensionExpression)
388396
{
389397
var entityParameter = Expression.Parameter(entityShaperExpression.Type);
390398
_variables.Add(entityParameter);
399+
if (entityShaperExpression.EntityType.GetMappingStrategy() == "TPC")
400+
{
401+
var concreteTypes = entityShaperExpression.EntityType.GetDerivedTypesInclusive().Where(e => !e.IsAbstract()).ToArray();
402+
// Single concrete TPC entity type won't have discriminator column.
403+
// We store the value here and inject it directly rather than reading from server.
404+
if (concreteTypes.Length == 1)
405+
_singleEntityTypeDiscriminatorValues[(ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression]
406+
= concreteTypes[0].ShortName();
407+
}
408+
391409
var entityMaterializationExpression = _parentVisitor.InjectEntityMaterializers(entityShaperExpression);
392410
entityMaterializationExpression = Visit(entityMaterializationExpression);
393411

@@ -867,12 +885,27 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
867885
{
868886
var property = methodCallExpression.Arguments[2].GetConstantValue<IProperty?>();
869887
var mappingParameter = (ParameterExpression)((MethodCallExpression)methodCallExpression.Arguments[0]).Object!;
870-
var projectionIndex = property == null
871-
? _entityTypeIdentifyingExpressionOffsets[mappingParameter]
872-
+ methodCallExpression.Arguments[1].GetConstantValue<int>()
873-
: _materializationContextBindings[mappingParameter][property];
874-
var projection = _selectExpression.Projection[projectionIndex];
888+
int projectionIndex;
889+
if (property == null)
890+
{
891+
// This is trying to read the computed discriminator value
892+
var storedInfo = _entityTypeIdentifyingExpressionInfo[mappingParameter];
893+
if (storedInfo is string s)
894+
{
895+
// If the value is fixed then there is single entity type and discriminator is not present in query
896+
// We just return the value as-is.
897+
return Expression.Constant(s);
898+
}
875899

900+
projectionIndex = (int)_entityTypeIdentifyingExpressionInfo[mappingParameter]
901+
+ methodCallExpression.Arguments[1].GetConstantValue<int>();
902+
}
903+
else
904+
{
905+
projectionIndex = _materializationContextBindings[mappingParameter][property];
906+
}
907+
908+
var projection = _selectExpression.Projection[projectionIndex];
876909
var nullable = IsNullableProjection(projection);
877910

878911
Check.DebugAssert(

src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs

+16-1
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,22 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression typeBinaryExp
771771
var discriminatorProperty = entityType.FindDiscriminatorProperty();
772772
if (discriminatorProperty == null)
773773
{
774-
// TPT
774+
if (entityType.GetMappingStrategy() == "TPC")
775+
{
776+
// Single concrete entity TPC doesn't have discriminator
777+
var concreteTypes = entityType.GetDerivedTypesInclusive().Where(e => !e.IsAbstract()).ToArray();
778+
if (concreteTypes.Length == 1)
779+
{
780+
if (concreteTypes[0].GetAllBaseTypesInclusive().Any(e => e.ClrType == typeBinaryExpression.TypeOperand))
781+
{
782+
return _sqlExpressionFactory.Constant(true);
783+
}
784+
// TODO: figure out if this code path is hit ever.
785+
throw new InvalidOperationException();
786+
}
787+
}
788+
789+
// TPT or TPC
775790
var discriminatorValues = derivedType.GetTptDiscriminatorValues();
776791
if (entityReferenceExpression.SubqueryEntity != null)
777792
{

src/EFCore.Relational/Query/SqlExpressionFactory.cs

+4
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,10 @@ public virtual SqlFragmentExpression Fragment(string sql)
576576
public virtual SqlConstantExpression Constant(object? value, RelationalTypeMapping? typeMapping = null)
577577
=> new(Expression.Constant(value), typeMapping);
578578

579+
/// <inheritdoc />
580+
public virtual SqlConstantExpression Constant(object? value, Type type, RelationalTypeMapping? typeMapping = null)
581+
=> new(Expression.Constant(value, type), typeMapping);
582+
579583
/// <inheritdoc />
580584
public virtual SelectExpression Select(SqlExpression? projection)
581585
=> new(projection);

0 commit comments

Comments
 (0)