Skip to content

Commit

Permalink
Query: Add support for TPC (#27961)
Browse files Browse the repository at this point in the history
Part of #3170
  • Loading branch information
smitpatel committed May 6, 2022
1 parent 8344932 commit dd7c329
Show file tree
Hide file tree
Showing 24 changed files with 11,918 additions and 1,791 deletions.
9 changes: 7 additions & 2 deletions src/EFCore.Relational/Query/EntityProjectionExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,13 @@ public virtual EntityProjectionExpression MakeNullable()
propertyExpressionMap[property] = columnExpression.MakeNullable();
}

// We don't need to process DiscriminatorExpression because they are already nullable
return new EntityProjectionExpression(EntityType, propertyExpressionMap, DiscriminatorExpression);
var discriminatorExpression = DiscriminatorExpression;
if (discriminatorExpression is ColumnExpression ce)
{
// if discriminator is column then we need to make it nullable
discriminatorExpression = ce.MakeNullable();
}
return new EntityProjectionExpression(EntityType, propertyExpressionMap, discriminatorExpression);
}

/// <summary>
Expand Down
11 changes: 10 additions & 1 deletion src/EFCore.Relational/Query/ISqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,18 @@ SqlFunctionExpression NiladicFunction(
/// </summary>
/// <param name="value">A value.</param>
/// <param name="typeMapping">The <see cref="RelationalTypeMapping" /> associated with the expression.</param>
/// <returns>An expression representing a LIKE in a SQL tree.</returns>
/// <returns>An expression representing a constant in a SQL tree.</returns>
SqlConstantExpression Constant(object? value, RelationalTypeMapping? typeMapping = null);

/// <summary>
/// Creates a new <see cref="SqlConstantExpression" /> which represents a constant in a SQL tree.
/// </summary>
/// <param name="value">A value.</param>
/// <param name="type">The type for the constant. Useful when value is null.</param>
/// <param name="typeMapping">The <see cref="RelationalTypeMapping" /> associated with the expression.</param>
/// <returns>An expression representing a constant in a SQL tree.</returns>
SqlConstantExpression Constant(object? value, Type type, RelationalTypeMapping? typeMapping = null);

/// <summary>
/// Creates a new <see cref="SqlFragmentExpression" /> which represents a SQL token.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,10 @@ private static readonly MethodInfo CollectionAccessorAddMethodInfo
private readonly IDictionary<ParameterExpression, IDictionary<IProperty, int>> _materializationContextBindings
= new Dictionary<ParameterExpression, IDictionary<IProperty, int>>();

private readonly IDictionary<ParameterExpression, int> _entityTypeIdentifyingExpressionOffsets
= new Dictionary<ParameterExpression, int>();
private readonly IDictionary<ParameterExpression, object> _entityTypeIdentifyingExpressionInfo
= new Dictionary<ParameterExpression, object>();
private readonly IDictionary<ProjectionBindingExpression, string> _singleEntityTypeDiscriminatorValues
= new Dictionary<ProjectionBindingExpression, string>();

public ShaperProcessingExpressionVisitor(
RelationalShapedQueryCompilingExpressionVisitor parentVisitor,
Expand Down Expand Up @@ -359,7 +361,13 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)

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


var updatedExpression = newExpression.Update(
new[] { Expression.Constant(ValueBuffer.Empty), newExpression.Arguments[1] });
Expand Down Expand Up @@ -388,6 +396,16 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
var entityParameter = Expression.Parameter(entityShaperExpression.Type);
_variables.Add(entityParameter);
if (entityShaperExpression.EntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy)
{
var concreteTypes = entityShaperExpression.EntityType.GetDerivedTypesInclusive().Where(e => !e.IsAbstract()).ToArray();
// Single concrete TPC entity type won't have discriminator column.
// We store the value here and inject it directly rather than reading from server.
if (concreteTypes.Length == 1)
_singleEntityTypeDiscriminatorValues[(ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression]
= concreteTypes[0].ShortName();
}

var entityMaterializationExpression = _parentVisitor.InjectEntityMaterializers(entityShaperExpression);
entityMaterializationExpression = Visit(entityMaterializationExpression);

Expand Down Expand Up @@ -867,12 +885,27 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
{
var property = methodCallExpression.Arguments[2].GetConstantValue<IProperty?>();
var mappingParameter = (ParameterExpression)((MethodCallExpression)methodCallExpression.Arguments[0]).Object!;
var projectionIndex = property == null
? _entityTypeIdentifyingExpressionOffsets[mappingParameter]
+ methodCallExpression.Arguments[1].GetConstantValue<int>()
: _materializationContextBindings[mappingParameter][property];
var projection = _selectExpression.Projection[projectionIndex];
int projectionIndex;
if (property == null)
{
// This is trying to read the computed discriminator value
var storedInfo = _entityTypeIdentifyingExpressionInfo[mappingParameter];
if (storedInfo is string s)
{
// If the value is fixed then there is single entity type and discriminator is not present in query
// We just return the value as-is.
return Expression.Constant(s);
}

projectionIndex = (int)_entityTypeIdentifyingExpressionInfo[mappingParameter]
+ methodCallExpression.Arguments[1].GetConstantValue<int>();
}
else
{
projectionIndex = _materializationContextBindings[mappingParameter][property];
}

var projection = _selectExpression.Projection[projectionIndex];
var nullable = IsNullableProjection(projection);

Check.DebugAssert(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,13 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression typeBinaryExp
var discriminatorProperty = entityType.FindDiscriminatorProperty();
if (discriminatorProperty == null)
{
// TPT
if (entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy
&& entityType.GetDerivedTypesInclusive().Count(e => !e.IsAbstract()) == 1)
{
return _sqlExpressionFactory.Constant(true);
}

// TPT or TPC
var discriminatorValues = derivedType.GetTptDiscriminatorValues();
if (entityReferenceExpression.SubqueryEntity != null)
{
Expand Down
4 changes: 4 additions & 0 deletions src/EFCore.Relational/Query/SqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,10 @@ public virtual SqlFragmentExpression Fragment(string sql)
public virtual SqlConstantExpression Constant(object? value, RelationalTypeMapping? typeMapping = null)
=> new(Expression.Constant(value), typeMapping);

/// <inheritdoc />
public virtual SqlConstantExpression Constant(object? value, Type type, RelationalTypeMapping? typeMapping = null)
=> new(Expression.Constant(value, type), typeMapping);

/// <inheritdoc />
public virtual SelectExpression Select(SqlExpression? projection)
=> new(projection);
Expand Down
Loading

0 comments on commit dd7c329

Please sign in to comment.