Skip to content

Commit

Permalink
Query: Add support for TPC
Browse files Browse the repository at this point in the history
Part of #3170
  • Loading branch information
smitpatel committed May 6, 2022
1 parent 3ea4803 commit 241d93e
Show file tree
Hide file tree
Showing 24 changed files with 11,912 additions and 1,789 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() == "TPC")
{
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,22 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression typeBinaryExp
var discriminatorProperty = entityType.FindDiscriminatorProperty();
if (discriminatorProperty == null)
{
// TPT
if (entityType.GetMappingStrategy() == "TPC")
{
// Single concrete entity TPC doesn't have discriminator
var concreteTypes = entityType.GetDerivedTypesInclusive().Where(e => !e.IsAbstract()).ToArray();
if (concreteTypes.Length == 1)
{
if (concreteTypes[0].GetAllBaseTypesInclusive().Any(e => e.ClrType == typeBinaryExpression.TypeOperand))
{
return _sqlExpressionFactory.Constant(true);
}
// TODO: figure out if this code path is hit ever.
throw new InvalidOperationException();
}
}

// 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 241d93e

Please sign in to comment.