Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
}

shaperBody = new JObjectInjectingExpressionVisitor().Visit(shaperBody);
shaperBody = InjectEntityMaterializers(shaperBody);
shaperBody = InjectStructuralTypeMaterializers(shaperBody);

if (shapedQueryExpression.QueryExpression is not SelectExpression selectExpression)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
variable = Parameter(shaper.StructuralType.ClrType);
_variables.Add(variable);
var innerShaper =
_inMemoryShapedQueryCompilingExpressionVisitor.InjectEntityMaterializers(shaper);
_inMemoryShapedQueryCompilingExpressionVisitor.InjectStructuralTypeMaterializers(shaper);
innerShaper = Visit(innerShaper);
_expressions.Add(Assign(variable, innerShaper));
_mapping[key] = variable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ public EntityFrameworkRelationalServicesBuilder(IServiceCollection serviceCollec
/// <returns>This builder, such that further calls can be chained.</returns>
public override EntityFrameworkServicesBuilder TryAddCoreServices()
{
TryAdd<IStructuralTypeMaterializerSource, RelationalStructuralTypeMaterializerSource>();
TryAdd<IParameterNameGeneratorFactory, ParameterNameGeneratorFactory>();
TryAdd<IComparer<IReadOnlyModificationCommand>, ModificationCommandComparer>();
TryAdd<IMigrationsIdGenerator, MigrationsIdGenerator>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ private static void CreateContainerColumn<TColumnMappingBase>(
}
else
{
complexType = ((IComplexType)mappedType);
complexType = (IComplexType)mappedType;
#pragma warning disable EF1001 // Internal EF Core API usage.
jsonColumn.IsNullable = complexType.ComplexProperty.IsNullable || complexType.ComplexProperty.GetChainToComplexProperty(fromEntity: true).Any(p => p.IsNullable);
#pragma warning restore EF1001 // Internal EF Core API usage.
Expand Down

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
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,9 @@
<data name="ExecuteUpdateSubqueryNotSupportedOverComplexTypes" xml:space="preserve">
<value>ExecuteUpdate is being used over a LINQ operator which isn't natively supported by the database; this cannot be translated because complex type '{complexType}' is projected out. Rewrite your query to project out the containing entity type instead.</value>
</data>
<data name="ExecuteUpdateOverJsonIsNotSupported" xml:space="preserve">
<value>ExecuteUpdate is being used over type '{structuralType}' which is mapped to JSON; ExecuteUpdate on JSON is not supported.</value>
</data>
<data name="ExplicitDefaultConstraintNamesNotSupportedForTpc" xml:space="preserve">
<value>Can't use explicitly named default constraints with TPC inheritance or entity splitting. Constraint name: '{explicitDefaultConstraintName}'.</value>
</data>
Expand Down
72 changes: 36 additions & 36 deletions src/EFCore.Relational/Query/CollectionResultExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,81 +13,81 @@ namespace Microsoft.EntityFrameworkCore.Query;
/// not used in application code.
/// </para>
/// </summary>
public class CollectionResultExpression : Expression, IPrintableExpression
/// <param name="queryExpression">Represents the server-side query expression for the collection.</param>
/// <param name="relationship">A navigation associated with this collection, if any.</param>
/// <param name="elementType">The clr type of individual elements in the collection.</param>
public class CollectionResultExpression(
Expression queryExpression,
IPropertyBase? relationship,
Type elementType)
: Expression, IPrintableExpression
{
/// <summary>
/// Creates a new instance of the <see cref="CollectionResultExpression" /> class.
/// </summary>
/// <param name="projectionBindingExpression">An expression representing how to get the subquery from SelectExpression to get the elements.</param>
/// <param name="navigation">A navigation associated with this collection, if any.</param>
/// <param name="elementType">The clr type of individual elements in the collection.</param>
public CollectionResultExpression(
ProjectionBindingExpression projectionBindingExpression,
INavigationBase? navigation,
Type elementType)
{
ProjectionBindingExpression = projectionBindingExpression;
Navigation = navigation;
ElementType = elementType;
}

/// <summary>
/// The expression to get the subquery for this collection.
/// </summary>
public virtual ProjectionBindingExpression ProjectionBindingExpression { get; }
public virtual Expression QueryExpression { get; } = queryExpression;

/// <summary>
/// The navigation if associated with the collection.
/// The relationship associated with the collection, if any.
/// </summary>
public virtual INavigationBase? Navigation { get; }
public virtual IPropertyBase? Relationship { get; } = relationship;

/// <summary>
/// The clr type of elements of the collection.
/// </summary>
public virtual Type ElementType { get; }
public virtual Type ElementType { get; } = elementType;

/// <inheritdoc />
public override Type Type
=> ProjectionBindingExpression.Type;
=> QueryExpression.Type;

/// <inheritdoc />
public override ExpressionType NodeType
=> ExpressionType.Extension;

/// <inheritdoc />
protected override Expression VisitChildren(ExpressionVisitor visitor)
{
var projectionBindingExpression = (ProjectionBindingExpression)visitor.Visit(ProjectionBindingExpression);

return Update(projectionBindingExpression);
}
=> Update(visitor.Visit(QueryExpression));

/// <summary>
/// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will
/// return this expression.
/// </summary>
/// <param name="projectionBindingExpression">The <see cref="ProjectionBindingExpression" /> property of the result.</param>
/// <param name="queryExpression">The <see cref="ProjectionBindingExpression" /> property of the result.</param>
/// <returns>This expression if no children changed, or an expression with the updated children.</returns>
public virtual CollectionResultExpression Update(ProjectionBindingExpression projectionBindingExpression)
=> projectionBindingExpression != ProjectionBindingExpression
? new CollectionResultExpression(projectionBindingExpression, Navigation, ElementType)
: this;
public virtual CollectionResultExpression Update(Expression queryExpression)
=> queryExpression == QueryExpression
? this
: new CollectionResultExpression(queryExpression, Relationship, ElementType);

/// <inheritdoc />
public virtual void Print(ExpressionPrinter expressionPrinter)
{
expressionPrinter.AppendLine("CollectionResultExpression:");
using (expressionPrinter.Indent())
{
expressionPrinter.Append("ProjectionBindingExpression:");
expressionPrinter.Visit(ProjectionBindingExpression);
expressionPrinter.Append("QueryExpression:");
expressionPrinter.Visit(QueryExpression);
expressionPrinter.AppendLine();
if (Navigation != null)

if (Relationship is not null)
{
expressionPrinter.Append("Navigation:").AppendLine(Navigation.ToString()!);
expressionPrinter.Append("Relationship:").AppendLine(Relationship.ToString()!);
}

expressionPrinter.Append("ElementType:").AppendLine(ElementType.ShortDisplayName());
}
}

/// <summary>
/// The expression to get the subquery for this collection.
/// </summary>
[Obsolete("Use QueryExpression instead.", error: true)]
public virtual ProjectionBindingExpression ProjectionBindingExpression { get; } = null!;

/// <summary>
/// The navigation if associated with the collection.
/// </summary>
[Obsolete("Use Relationship instead.", error: true)]
public virtual INavigationBase? Navigation { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public readonly struct QueryableJsonProjectionInfo
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public QueryableJsonProjectionInfo(
Dictionary<IProperty, int> propertyIndexMap,
Dictionary<IPropertyBase, int> propertyIndexMap,
List<(JsonProjectionInfo, INavigation)> childrenProjectionInfo)
{
PropertyIndexMap = propertyIndexMap;
Expand All @@ -34,7 +34,7 @@ public QueryableJsonProjectionInfo(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </remarks>
public IDictionary<IProperty, int> PropertyIndexMap { get; }
public IDictionary<IPropertyBase, int> PropertyIndexMap { get; }

/// <summary>
/// Information needed to construct each child JSON entity.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio
// expression.Type here will be List<T>
return new CollectionResultExpression(
new ProjectionBindingExpression(_selectExpression, _clientProjections.Count - 1, expression.Type),
navigation: null,
relationship: null,
methodCallExpression.Method.GetGenericArguments()[0]);
}
}
Expand All @@ -219,7 +219,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio
_selectExpression, _clientProjections.Count - 1, type);
return subquery.ResultCardinality == ResultCardinality.Enumerable
? new CollectionResultExpression(
projectionBindingExpression, navigation: null, subquery.ShaperExpression.Type)
projectionBindingExpression, relationship: null, subquery.ShaperExpression.Type)
: projectionBindingExpression;
}
}
Expand All @@ -242,6 +242,9 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio
case RelationalStructuralTypeShaperExpression { StructuralType: IComplexType } shaper:
return base.Visit(shaper);

case CollectionResultExpression collectionResult:
return base.Visit(collectionResult);

case null or RelationalStructuralTypeShaperExpression { StructuralType: IEntityType }:
return QueryCompilationContext.NotTranslatedExpression;

Expand Down Expand Up @@ -386,16 +389,16 @@ protected override Expression VisitExtension(Expression extensionExpression)
return QueryCompilationContext.NotTranslatedExpression;
}

case CollectionResultExpression collectionResultExpression:
case CollectionResultExpression { QueryExpression: ProjectionBindingExpression projectionBindingExpression } collectionResultExpression:
{
// TODO this should not be needed at some point, we shouldn't be revisit same projection.
// TODO this should not be needed at some point, we shouldn't be revisiting same projection.
// This happens because we don't process result selector for Join/SelectMany directly.
if (_indexBasedBinding)
{
Check.DebugAssert(
ReferenceEquals(_selectExpression, collectionResultExpression.ProjectionBindingExpression.QueryExpression),
ReferenceEquals(_selectExpression, projectionBindingExpression.QueryExpression),
"The projection should belong to same select expression.");
var mappedProjection = _selectExpression.GetProjection(collectionResultExpression.ProjectionBindingExpression);
var mappedProjection = _selectExpression.GetProjection(projectionBindingExpression);
_clientProjections!.Add(mappedProjection);

return collectionResultExpression.Update(
Expand All @@ -406,6 +409,21 @@ protected override Expression VisitExtension(Expression extensionExpression)
return QueryCompilationContext.NotTranslatedExpression;
}

case CollectionResultExpression { QueryExpression: JsonQueryExpression jsonQuery } collectionResult:
{
if (_indexBasedBinding)
{
_clientProjections!.Add(jsonQuery);
}
else
{
_projectionMapping[_projectionMembers.Peek()] = jsonQuery;
}

return collectionResult.Update(
new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), collectionResult.Type));
}

default:
throw new InvalidOperationException(CoreStrings.TranslationFailed(extensionExpression.Print()));
}
Expand Down Expand Up @@ -633,7 +651,9 @@ private static Expression MatchTypes(Expression expression, Type targetType)
if (targetType != expression.Type
&& targetType.TryGetElementType(typeof(IQueryable<>)) == null)
{
Check.DebugAssert(targetType.MakeNullable() == expression.Type, "expression.Type must be nullable of targetType");
Check.DebugAssert(
targetType.MakeNullable() == expression.Type,
$"expression has type {expression.Type.Name}, but must be nullable over {targetType.Name}");

expression = Expression.Convert(expression, targetType);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Query.Internal;

#pragma warning disable EF1001 // EntityMaterializerSource is pubternal

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class RelationalStructuralTypeMaterializerSource(StructuralTypeMaterializerSourceDependencies dependencies)
: StructuralTypeMaterializerSource(dependencies)
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected override void AddInitializeExpression(
IPropertyBase property,
ParameterBindingInfo bindingInfo,
Expression instanceVariable,
MethodCallExpression valueBufferExpression,
List<Expression> blockExpressions)
{
// JSON complex properties are not handled in the initial materialization expression, since they're not
// simply e.g. DbDataReader.GetFieldValue<>() calls. So they're handled afterwards in the shaper, and need
// to be skipped here.
if (property is IComplexProperty { ComplexType: var complexType } && complexType.IsMappedToJson())
{
return;
}

base.AddInitializeExpression(property, bindingInfo, instanceVariable, valueBufferExpression, blockExpressions);
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
public static readonly MethodInfo MaterializeJsonComplexTypeMethod
= typeof(RelationalStructuralTypeMaterializerSource).GetTypeInfo().GetDeclaredMethod(nameof(MaterializeJsonComplexType))!;

private static T MaterializeJsonComplexType<T>(in ValueBuffer valueBuffer, IComplexProperty complexProperty)
=> throw new UnreachableException();
}
Loading