diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
index 37415cb8fb3..224dbf03151 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
@@ -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)
{
diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs
index 59c7cde2fe0..7f435655384 100644
--- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs
+++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs
@@ -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;
diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs
index d0ee126a44c..ca9d17a642f 100644
--- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs
+++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs
@@ -131,6 +131,7 @@ public EntityFrameworkRelationalServicesBuilder(IServiceCollection serviceCollec
/// This builder, such that further calls can be chained.
public override EntityFrameworkServicesBuilder TryAddCoreServices()
{
+ TryAdd();
TryAdd();
TryAdd, ModificationCommandComparer>();
TryAdd();
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
index b14858e928a..45aea183d1a 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
@@ -624,7 +624,7 @@ private static void CreateContainerColumn(
}
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.
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index 92bfc9929e6..5485801c0e6 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -865,6 +865,14 @@ public static string ExecuteUpdateSubqueryNotSupportedOverComplexTypes(object? c
GetString("ExecuteUpdateSubqueryNotSupportedOverComplexTypes", nameof(complexType)),
complexType);
+ ///
+ /// ExecuteUpdate is being used over type '{structuralType}' which is mapped to JSON; ExecuteUpdate on JSON is not supported.
+ ///
+ public static string ExecuteUpdateOverJsonIsNotSupported(object? structuralType)
+ => string.Format(
+ GetString("ExecuteUpdateOverJsonIsNotSupported", nameof(structuralType)),
+ structuralType);
+
///
/// Can't use explicitly named default constraints with TPC inheritance or entity splitting. Constraint name: '{explicitDefaultConstraintName}'.
///
@@ -2192,7 +2200,7 @@ public static string UnsupportedOperatorForSqlExpression(object? nodeType, objec
nodeType, expressionType);
///
- /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'.
+ /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'.
///
public static string UnsupportedPropertyType(object? entity, object? property, object? clrType)
=> string.Format(
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index 3019cfaf63c..5ef534dfacf 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -442,6 +442,9 @@
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.
+
+ ExecuteUpdate is being used over type '{structuralType}' which is mapped to JSON; ExecuteUpdate on JSON is not supported.
+
Can't use explicitly named default constraints with TPC inheritance or entity splitting. Constraint name: '{explicitDefaultConstraintName}'.
diff --git a/src/EFCore.Relational/Query/CollectionResultExpression.cs b/src/EFCore.Relational/Query/CollectionResultExpression.cs
index 99c9ef63ea4..17aaee5be67 100644
--- a/src/EFCore.Relational/Query/CollectionResultExpression.cs
+++ b/src/EFCore.Relational/Query/CollectionResultExpression.cs
@@ -13,42 +13,33 @@ namespace Microsoft.EntityFrameworkCore.Query;
/// not used in application code.
///
///
-public class CollectionResultExpression : Expression, IPrintableExpression
+/// Represents the server-side query expression for the collection.
+/// A navigation associated with this collection, if any.
+/// The clr type of individual elements in the collection.
+public class CollectionResultExpression(
+ Expression queryExpression,
+ IPropertyBase? relationship,
+ Type elementType)
+ : Expression, IPrintableExpression
{
- ///
- /// Creates a new instance of the class.
- ///
- /// An expression representing how to get the subquery from SelectExpression to get the elements.
- /// A navigation associated with this collection, if any.
- /// The clr type of individual elements in the collection.
- public CollectionResultExpression(
- ProjectionBindingExpression projectionBindingExpression,
- INavigationBase? navigation,
- Type elementType)
- {
- ProjectionBindingExpression = projectionBindingExpression;
- Navigation = navigation;
- ElementType = elementType;
- }
-
///
/// The expression to get the subquery for this collection.
///
- public virtual ProjectionBindingExpression ProjectionBindingExpression { get; }
+ public virtual Expression QueryExpression { get; } = queryExpression;
///
- /// The navigation if associated with the collection.
+ /// The relationship associated with the collection, if any.
///
- public virtual INavigationBase? Navigation { get; }
+ public virtual IPropertyBase? Relationship { get; } = relationship;
///
/// The clr type of elements of the collection.
///
- public virtual Type ElementType { get; }
+ public virtual Type ElementType { get; } = elementType;
///
public override Type Type
- => ProjectionBindingExpression.Type;
+ => QueryExpression.Type;
///
public override ExpressionType NodeType
@@ -56,22 +47,18 @@ public override ExpressionType NodeType
///
protected override Expression VisitChildren(ExpressionVisitor visitor)
- {
- var projectionBindingExpression = (ProjectionBindingExpression)visitor.Visit(ProjectionBindingExpression);
-
- return Update(projectionBindingExpression);
- }
+ => Update(visitor.Visit(QueryExpression));
///
/// 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.
///
- /// The property of the result.
+ /// The property of the result.
/// This expression if no children changed, or an expression with the updated children.
- 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);
///
public virtual void Print(ExpressionPrinter expressionPrinter)
@@ -79,15 +66,28 @@ 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());
}
}
+
+ ///
+ /// The expression to get the subquery for this collection.
+ ///
+ [Obsolete("Use QueryExpression instead.", error: true)]
+ public virtual ProjectionBindingExpression ProjectionBindingExpression { get; } = null!;
+
+ ///
+ /// The navigation if associated with the collection.
+ ///
+ [Obsolete("Use Relationship instead.", error: true)]
+ public virtual INavigationBase? Navigation { get; }
}
diff --git a/src/EFCore.Relational/Query/Internal/QueryableJsonProjectionInfo.cs b/src/EFCore.Relational/Query/Internal/QueryableJsonProjectionInfo.cs
index 12b168b43c3..7ca6c3797c4 100644
--- a/src/EFCore.Relational/Query/Internal/QueryableJsonProjectionInfo.cs
+++ b/src/EFCore.Relational/Query/Internal/QueryableJsonProjectionInfo.cs
@@ -18,7 +18,7 @@ public readonly struct QueryableJsonProjectionInfo
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public QueryableJsonProjectionInfo(
- Dictionary propertyIndexMap,
+ Dictionary propertyIndexMap,
List<(JsonProjectionInfo, INavigation)> childrenProjectionInfo)
{
PropertyIndexMap = propertyIndexMap;
@@ -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.
///
- public IDictionary PropertyIndexMap { get; }
+ public IDictionary PropertyIndexMap { get; }
///
/// Information needed to construct each child JSON entity.
diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs
index 937410dd762..610c974c741 100644
--- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs
@@ -198,7 +198,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio
// expression.Type here will be List
return new CollectionResultExpression(
new ProjectionBindingExpression(_selectExpression, _clientProjections.Count - 1, expression.Type),
- navigation: null,
+ relationship: null,
methodCallExpression.Method.GetGenericArguments()[0]);
}
}
@@ -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;
}
}
@@ -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;
@@ -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(
@@ -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()));
}
@@ -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);
}
diff --git a/src/EFCore.Relational/Query/Internal/RelationalStructuralTypeMaterializerSource.cs b/src/EFCore.Relational/Query/Internal/RelationalStructuralTypeMaterializerSource.cs
new file mode 100644
index 00000000000..43400fe11c1
--- /dev/null
+++ b/src/EFCore.Relational/Query/Internal/RelationalStructuralTypeMaterializerSource.cs
@@ -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
+
+///
+/// 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.
+///
+public class RelationalStructuralTypeMaterializerSource(StructuralTypeMaterializerSourceDependencies dependencies)
+ : StructuralTypeMaterializerSource(dependencies)
+{
+ ///
+ /// 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.
+ ///
+ protected override void AddInitializeExpression(
+ IPropertyBase property,
+ ParameterBindingInfo bindingInfo,
+ Expression instanceVariable,
+ MethodCallExpression valueBufferExpression,
+ List 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);
+ }
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public static readonly MethodInfo MaterializeJsonComplexTypeMethod
+ = typeof(RelationalStructuralTypeMaterializerSource).GetTypeInfo().GetDeclaredMethod(nameof(MaterializeJsonComplexType))!;
+
+ private static T MaterializeJsonComplexType(in ValueBuffer valueBuffer, IComplexProperty complexProperty)
+ => throw new UnreachableException();
+}
diff --git a/src/EFCore.Relational/Query/JsonQueryExpression.cs b/src/EFCore.Relational/Query/JsonQueryExpression.cs
index 92b2440bef6..72144dfc179 100644
--- a/src/EFCore.Relational/Query/JsonQueryExpression.cs
+++ b/src/EFCore.Relational/Query/JsonQueryExpression.cs
@@ -19,19 +19,19 @@ public class JsonQueryExpression : Expression, IPrintableExpression
///
/// Creates a new instance of the class.
///
- /// An entity type being represented by this expression.
- /// A column containing JSON value.
- /// A map of key properties and columns they map to in the database.
- /// A type of the element represented by this expression.
- /// A value indicating whether this expression represents a collection or not.
+ /// The structural type represented by this expression.
+ /// A column containing the JSON value.
+ /// For owned entities, a map of key properties and columns they map to in the database. For complex types, .
+ /// The CLR represented by this expression.
+ /// Whether this expression represents a collection.
public JsonQueryExpression(
- IEntityType entityType,
+ ITypeBase structuralType,
ColumnExpression jsonColumn,
- IReadOnlyDictionary keyPropertyMap,
+ IReadOnlyDictionary? keyPropertyMap,
Type type,
bool collection)
: this(
- entityType,
+ structuralType,
jsonColumn,
keyPropertyMap,
path: [],
@@ -41,18 +41,28 @@ public JsonQueryExpression(
{
}
- private JsonQueryExpression(
- IEntityType entityType,
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The structural type represented by this expression.
+ /// A column containing the JSON value.
+ /// For owned entities, a map of key properties and columns they map to in the database. For complex types, .
+ /// The list of path segments leading to the entity from the root of the JSON stored in the column.
+ /// The CLR represented by this expression.
+ /// Whether this expression represents a collection.
+ /// Whether this expression is nullable.
+ public JsonQueryExpression(
+ ITypeBase structuralType,
ColumnExpression jsonColumn,
- IReadOnlyDictionary keyPropertyMap,
+ IReadOnlyDictionary? keyPropertyMap,
IReadOnlyList path,
Type type,
bool collection,
bool nullable)
{
- Check.DebugAssert(entityType.FindPrimaryKey() != null, "primary key is null.");
+ Check.DebugAssert(structuralType is not IEntityType entityType || entityType.FindPrimaryKey() is not null, "JsonQueryExpression over keyless entity type");
- EntityType = entityType;
+ StructuralType = structuralType;
JsonColumn = jsonColumn;
IsCollection = collection;
KeyPropertyMap = keyPropertyMap;
@@ -62,17 +72,17 @@ private JsonQueryExpression(
}
///
- /// The entity type being represented by this expression.
+ /// The structural type represented by this expression.
///
- public virtual IEntityType EntityType { get; }
+ public virtual ITypeBase StructuralType { get; }
///
- /// The column containing JSON value.
+ /// The column containing the JSON value.
///
public virtual ColumnExpression JsonColumn { get; }
///
- /// The value indicating whether this expression represents a collection.
+ /// Whether this expression represents a collection.
///
public virtual bool IsCollection { get; }
@@ -82,7 +92,7 @@ private JsonQueryExpression(
public virtual IReadOnlyList Path { get; }
///
- /// The value indicating whether this expression is nullable.
+ /// Whether this expression is nullable.
///
public virtual bool IsNullable { get; }
@@ -93,7 +103,7 @@ private JsonQueryExpression(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
[EntityFrameworkInternal]
- public virtual IReadOnlyDictionary KeyPropertyMap { get; }
+ public virtual IReadOnlyDictionary? KeyPropertyMap { get; }
///
public override ExpressionType NodeType
@@ -107,66 +117,102 @@ public override ExpressionType NodeType
///
public virtual SqlExpression BindProperty(IProperty property)
{
- if (!EntityType.IsAssignableFrom(property.DeclaringType)
- && !property.DeclaringType.IsAssignableFrom(EntityType))
+ if (!StructuralType.IsAssignableFrom(property.DeclaringType)
+ && !property.DeclaringType.IsAssignableFrom(StructuralType))
{
throw new InvalidOperationException(
- RelationalStrings.UnableToBindMemberToEntityProjection("property", property.Name, EntityType.DisplayName()));
+ RelationalStrings.UnableToBindMemberToEntityProjection("property", property.Name, StructuralType.DisplayName()));
}
- if (KeyPropertyMap.TryGetValue(property, out var match))
+ if (KeyPropertyMap?.TryGetValue(property, out var match) == true)
{
return match;
}
- var newPath = Path.ToList();
- newPath.Add(new PathSegment(property.GetJsonPropertyName()!));
-
return new JsonScalarExpression(
JsonColumn,
- newPath,
+ [.. Path, new(property.GetJsonPropertyName()!)],
property.ClrType.UnwrapNullableType(),
property.FindRelationalTypeMapping()!,
IsNullable || property.IsNullable);
}
///
- /// Binds a navigation with this JSON query expression to get the SQL representation.
+ /// Binds a relationship with this JSON query expression to get the SQL representation.
///
- /// The navigation to bind.
- /// An JSON query expression for the target entity type of the navigation.
- public virtual JsonQueryExpression BindNavigation(INavigation navigation)
+ /// The navigation or complex property to bind.
+ /// An JSON query expression for the target entity or complex type.
+ public virtual JsonQueryExpression BindRelationship(IPropertyBase relationship)
{
- if (navigation.ForeignKey.DependentToPrincipal == navigation)
+ switch (relationship)
{
- // issue #28645
- throw new InvalidOperationException(
- RelationalStrings.JsonCantNavigateToParentEntity(
- navigation.ForeignKey.DeclaringEntityType.DisplayName(),
- navigation.ForeignKey.PrincipalEntityType.DisplayName(),
- navigation.Name));
- }
+ case INavigation navigation:
+ {
+ if (StructuralType is not IEntityType entityType)
+ {
+ throw new UnreachableException("Navigation on complex JSON type");
+ }
+
+ Check.DebugAssert(KeyPropertyMap is not null);
+
+ if (navigation.ForeignKey.DependentToPrincipal == navigation)
+ {
+ // issue #28645
+ throw new InvalidOperationException(
+ RelationalStrings.JsonCantNavigateToParentEntity(
+ navigation.ForeignKey.DeclaringEntityType.DisplayName(),
+ navigation.ForeignKey.PrincipalEntityType.DisplayName(),
+ navigation.Name));
+ }
+
+ var targetEntityType = navigation.TargetEntityType;
+ var newPath = Path.ToList();
+ newPath.Add(new PathSegment(targetEntityType.GetJsonPropertyName()!));
+
+ var newKeyPropertyMap = new Dictionary();
+ var targetPrimaryKeyProperties = targetEntityType.FindPrimaryKey()!.Properties.Take(KeyPropertyMap.Count);
+ var sourcePrimaryKeyProperties = entityType.FindPrimaryKey()!.Properties.Take(KeyPropertyMap.Count);
+ foreach (var (target, source) in targetPrimaryKeyProperties.Zip(sourcePrimaryKeyProperties, (t, s) => (t, s)))
+ {
+ newKeyPropertyMap[target] = KeyPropertyMap[source];
+ }
+
+ return new JsonQueryExpression(
+ targetEntityType,
+ JsonColumn,
+ newKeyPropertyMap,
+ newPath,
+ navigation.ClrType,
+ navigation.IsCollection,
+ IsNullable || !navigation.ForeignKey.IsRequiredDependent);
+ }
- var targetEntityType = navigation.TargetEntityType;
- var newPath = Path.ToList();
- newPath.Add(new PathSegment(targetEntityType.GetJsonPropertyName()!));
+ case IComplexProperty complexProperty:
+ {
+ if (StructuralType is not IComplexType complexType)
+ {
+ throw new UnreachableException("Navigation on complex JSON type");
+ }
+
+ Check.DebugAssert(KeyPropertyMap is null);
+
+ var targetComplexType = complexProperty.ComplexType;
+ var newPath = Path.ToList();
+ newPath.Add(new PathSegment(targetComplexType.GetJsonPropertyName()!));
+
+ return new JsonQueryExpression(
+ targetComplexType,
+ JsonColumn,
+ keyPropertyMap: null,
+ newPath,
+ complexProperty.ClrType,
+ complexProperty.IsCollection,
+ IsNullable || complexProperty.IsNullable);
+ }
- var newKeyPropertyMap = new Dictionary();
- var targetPrimaryKeyProperties = targetEntityType.FindPrimaryKey()!.Properties.Take(KeyPropertyMap.Count);
- var sourcePrimaryKeyProperties = EntityType.FindPrimaryKey()!.Properties.Take(KeyPropertyMap.Count);
- foreach (var (target, source) in targetPrimaryKeyProperties.Zip(sourcePrimaryKeyProperties, (t, s) => (t, s)))
- {
- newKeyPropertyMap[target] = KeyPropertyMap[source];
+ default:
+ throw new UnreachableException();
}
-
- return new JsonQueryExpression(
- targetEntityType,
- JsonColumn,
- newKeyPropertyMap,
- newPath,
- navigation.ClrType,
- navigation.IsCollection,
- IsNullable || !navigation.ForeignKey.IsRequiredDependent);
}
///
@@ -183,11 +229,11 @@ public virtual JsonQueryExpression BindCollectionElement(SqlExpression collectio
newPath.Add(new PathSegment(collectionIndexExpression));
return new JsonQueryExpression(
- EntityType,
+ StructuralType,
JsonColumn,
KeyPropertyMap,
newPath,
- EntityType.ClrType,
+ StructuralType.ClrType,
collection: false,
// TODO: computing nullability might be more complicated when we allow strict mode
// see issue #28656
@@ -199,22 +245,14 @@ public virtual JsonQueryExpression BindCollectionElement(SqlExpression collectio
///
/// A new expression which has property set to true.
public virtual JsonQueryExpression MakeNullable()
- {
- var keyPropertyMap = new Dictionary();
- foreach (var (property, columnExpression) in KeyPropertyMap)
- {
- keyPropertyMap[property] = columnExpression.MakeNullable();
- }
-
- return new JsonQueryExpression(
- EntityType,
+ => new JsonQueryExpression(
+ StructuralType,
JsonColumn.MakeNullable(),
- keyPropertyMap,
+ KeyPropertyMap?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.MakeNullable()),
Path,
Type,
IsCollection,
nullable: true);
- }
///
public virtual void Print(ExpressionPrinter expressionPrinter)
@@ -229,6 +267,12 @@ public virtual void Print(ExpressionPrinter expressionPrinter)
protected override Expression VisitChildren(ExpressionVisitor visitor)
{
var jsonColumn = (ColumnExpression)visitor.Visit(JsonColumn);
+
+ if (KeyPropertyMap is null)
+ {
+ return Update(jsonColumn, keyPropertyMap: null);
+ }
+
var newKeyPropertyMap = new Dictionary();
foreach (var (property, column) in KeyPropertyMap)
{
@@ -247,12 +291,15 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
/// This expression if no children changed, or an expression with the updated children.
public virtual JsonQueryExpression Update(
ColumnExpression jsonColumn,
- IReadOnlyDictionary keyPropertyMap)
- => jsonColumn != JsonColumn
- || keyPropertyMap.Count != KeyPropertyMap.Count
- || keyPropertyMap.Zip(KeyPropertyMap, (n, o) => n.Value != o.Value).Any(x => x)
- ? new JsonQueryExpression(EntityType, jsonColumn, keyPropertyMap, Path, Type, IsCollection, IsNullable)
- : this;
+ IReadOnlyDictionary? keyPropertyMap)
+ => (jsonColumn == JsonColumn
+ && ((keyPropertyMap is null && KeyPropertyMap is null)
+ || (keyPropertyMap is not null
+ && KeyPropertyMap is not null
+ && keyPropertyMap.Count == KeyPropertyMap.Count
+ && KeyPropertyMapEquals(keyPropertyMap))))
+ ? this
+ : new JsonQueryExpression(StructuralType, jsonColumn, keyPropertyMap, Path, Type, IsCollection, IsNullable);
///
public override bool Equals(object? obj)
@@ -262,16 +309,21 @@ public override bool Equals(object? obj)
&& Equals(jsonQueryExpression));
private bool Equals(JsonQueryExpression jsonQueryExpression)
- => EntityType.Equals(jsonQueryExpression.EntityType)
+ => StructuralType.Equals(jsonQueryExpression.StructuralType)
&& JsonColumn.Equals(jsonQueryExpression.JsonColumn)
&& IsCollection.Equals(jsonQueryExpression.IsCollection)
&& IsNullable == jsonQueryExpression.IsNullable
&& Path.SequenceEqual(jsonQueryExpression.Path)
&& KeyPropertyMapEquals(jsonQueryExpression.KeyPropertyMap);
- private bool KeyPropertyMapEquals(IReadOnlyDictionary other)
+ private bool KeyPropertyMapEquals(IReadOnlyDictionary? other)
{
- if (KeyPropertyMap.Count != other.Count)
+ if (KeyPropertyMap is null && other is null)
+ {
+ return true;
+ }
+
+ if (KeyPropertyMap is null || other is null || KeyPropertyMap.Count != other.Count)
{
return false;
}
@@ -290,5 +342,5 @@ private bool KeyPropertyMapEquals(IReadOnlyDictionary
public override int GetHashCode()
// not incorporating _keyPropertyMap into the hash, too much work
- => HashCode.Combine(EntityType, JsonColumn, IsCollection, Path, IsNullable);
+ => HashCode.Combine(StructuralType, JsonColumn, IsCollection, Path, IsNullable);
}
diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs
index a3676bc23ff..8dd443fe376 100644
--- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs
@@ -586,19 +586,19 @@ protected virtual SelectExpression CreateSelect(
throw new ArgumentException(RelationalStrings.SelectCanOnlyBeBuiltOnCollectionJsonQuery, nameof(jsonQueryExpression));
}
- var entityType = jsonQueryExpression.EntityType;
+ var structuralType = jsonQueryExpression.StructuralType;
Check.DebugAssert(
- entityType.BaseType is null && !entityType.GetDirectlyDerivedTypes().Any(),
+ structuralType.BaseType is null && !structuralType.GetDirectlyDerivedTypes().Any(),
"Inheritance encountered inside a JSON document");
// Create a dictionary mapping all properties to their ColumnExpressions, for the SelectExpression's projection.
var propertyExpressions = new Dictionary();
- foreach (var property in entityType.GetPropertiesInHierarchy())
+ foreach (var property in structuralType.GetPropertiesInHierarchy())
{
- // also adding column(s) representing key of the parent (non-JSON) entity, on top of all the projections from OPENJSON/json_each/etc.
- if (jsonQueryExpression.KeyPropertyMap.TryGetValue(property, out var ownerKeyColumn))
+ // For owned JSON mapping, add column(s) representing key of the parent (non-JSON) entity, on top of all the projections from OPENJSON/json_each/etc.
+ if (jsonQueryExpression.KeyPropertyMap?.TryGetValue(property, out var ownerKeyColumn) == true)
{
propertyExpressions[property] = ownerKeyColumn;
continue;
@@ -610,67 +610,68 @@ protected virtual SelectExpression CreateSelect(
{
propertyExpressions[property] = CreateColumnExpression(
tableExpressionBase, jsonPropertyName, property.ClrType, property.GetRelationalTypeMapping(),
- /*jsonQueryExpression.IsNullable || */property.IsNullable);
+ /* jsonQueryExpression.IsNullable || */ property.IsNullable); // TODO:
}
}
- var table = entityType.GetViewOrTableMappings().SingleOrDefault()?.Table ?? entityType.GetDefaultMappings().Single().Table;
+ var table = structuralType.GetViewOrTableMappings().SingleOrDefault()?.Table ?? structuralType.GetDefaultMappings().Single().Table;
var tableAlias = tableExpressionBase.Alias!;
-
- // TODO: We'll need to make sure this is correct when we add support for JSON complex types, #31252
var tableMap = new Dictionary { [table] = tableAlias };
- var projection = new StructuralTypeProjectionExpression(
- entityType,
- propertyExpressions,
- tableMap);
+ var projection = new StructuralTypeProjectionExpression(structuralType, propertyExpressions, tableMap);
- var containerColumnName = entityType.GetContainerColumnName()!;
- var containerColumn = table.FindColumn(containerColumnName)!;
- var containerColumnTypeMapping = containerColumn.StoreTypeMapping;
- foreach (var ownedJsonNavigation in entityType.GetNavigationsInHierarchy()
- .Where(
- n => n.ForeignKey.IsOwnership
- && n.TargetEntityType.IsMappedToJson()
- && n.ForeignKey.PrincipalToDependent == n))
+ // Go over all owned JSON navigations and pre-populate bindings for them - these get used later if the LINQ query binds to them.
+ // Note that we don't need to do the same for complex properties, are these are managed lazily: when a complex property is bound,
+ // we generate everything we need at that point.
+ if (structuralType is IEntityType entityType)
{
- var targetEntityType = ownedJsonNavigation.TargetEntityType;
- var jsonNavigationName = ownedJsonNavigation.TargetEntityType.GetJsonPropertyName();
- Check.DebugAssert(jsonNavigationName is not null, "Invalid navigation found on JSON-mapped entity");
- var isNullable = containerColumn.IsNullable
- || !ownedJsonNavigation.ForeignKey.IsRequiredDependent
- || ownedJsonNavigation.IsCollection;
-
- // The TableExpressionBase represents a relational expansion of the JSON collection. We now need a ColumnExpression to represent
- // the specific JSON property (projected as a relational column) which holds the JSON subtree for the target entity.
- var column = new ColumnExpression(
- jsonNavigationName,
- tableAlias,
- containerColumnTypeMapping.ClrType,
- containerColumnTypeMapping,
- isNullable);
-
- // need to remap key property map to use target entity key properties
- var newKeyPropertyMap = new Dictionary();
- var targetPrimaryKeyProperties = targetEntityType.FindPrimaryKey()!.Properties.Take(jsonQueryExpression.KeyPropertyMap.Count);
- var sourcePrimaryKeyProperties =
- jsonQueryExpression.EntityType.FindPrimaryKey()!.Properties.Take(jsonQueryExpression.KeyPropertyMap.Count);
- foreach (var (target, source) in targetPrimaryKeyProperties.Zip(sourcePrimaryKeyProperties, (t, s) => (t, s)))
+ var containerColumnName = structuralType.GetContainerColumnName()!;
+ var containerColumn = table.FindColumn(containerColumnName)!;
+ var containerColumnTypeMapping = containerColumn.StoreTypeMapping;
+ foreach (var ownedJsonNavigation in entityType.GetNavigationsInHierarchy()
+ .Where(
+ n => n.ForeignKey.IsOwnership
+ && n.TargetEntityType.IsMappedToJson()
+ && n.ForeignKey.PrincipalToDependent == n))
{
- newKeyPropertyMap[target] = jsonQueryExpression.KeyPropertyMap[source];
- }
+ var targetEntityType = ownedJsonNavigation.TargetEntityType;
+ var jsonNavigationName = ownedJsonNavigation.TargetEntityType.GetJsonPropertyName();
+ Check.DebugAssert(jsonNavigationName is not null, "Invalid navigation found on JSON-mapped entity");
+ var isNullable = containerColumn.IsNullable
+ || !ownedJsonNavigation.ForeignKey.IsRequiredDependent
+ || ownedJsonNavigation.IsCollection;
+
+ // The TableExpressionBase represents a relational expansion of the JSON collection. We now need a ColumnExpression to represent
+ // the specific JSON property (projected as a relational column) which holds the JSON subtree for the target entity.
+ var column = new ColumnExpression(
+ jsonNavigationName,
+ tableAlias,
+ containerColumnTypeMapping.ClrType,
+ containerColumnTypeMapping,
+ isNullable);
+
+ // need to remap key property map to use target entity key properties
+ var newKeyPropertyMap = new Dictionary();
+ var targetPrimaryKeyProperties = targetEntityType.FindPrimaryKey()!.Properties.Take(jsonQueryExpression.KeyPropertyMap!.Count);
+ var sourcePrimaryKeyProperties =
+ entityType.FindPrimaryKey()!.Properties.Take(jsonQueryExpression.KeyPropertyMap.Count);
+ foreach (var (target, source) in targetPrimaryKeyProperties.Zip(sourcePrimaryKeyProperties, (t, s) => (t, s)))
+ {
+ newKeyPropertyMap[target] = jsonQueryExpression.KeyPropertyMap[source];
+ }
- var entityShaperExpression = new RelationalStructuralTypeShaperExpression(
- targetEntityType,
- new JsonQueryExpression(
+ var entityShaperExpression = new RelationalStructuralTypeShaperExpression(
targetEntityType,
- column,
- newKeyPropertyMap,
- ownedJsonNavigation.ClrType,
- ownedJsonNavigation.IsCollection),
- isNullable);
-
- projection.AddNavigationBinding(ownedJsonNavigation, entityShaperExpression);
+ new JsonQueryExpression(
+ targetEntityType,
+ column,
+ newKeyPropertyMap,
+ ownedJsonNavigation.ClrType,
+ ownedJsonNavigation.IsCollection),
+ isNullable);
+
+ projection.AddNavigationBinding(ownedJsonNavigation, entityShaperExpression);
+ }
}
var identifierColumn = new ColumnExpression(
diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs
index 308cf9489b1..456d7a9dde6 100644
--- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs
@@ -156,7 +156,7 @@ bool TranslateSetters(
break;
}
- // TODO: This is for column flattening; implement JSON complex type support as well.
+ // TODO: This is for column flattening; implement JSON complex type support as well (#28766)
case StructuralTypeShaperExpression
{
StructuralType: IComplexType complexType,
@@ -167,6 +167,11 @@ bool TranslateSetters(
propertyBase is IComplexProperty complexProperty && complexProperty.ComplexType == complexType,
"PropertyBase should be a complex property referring to the correct complex type");
+ if (complexType.IsMappedToJson())
+ {
+ throw new InvalidOperationException(RelationalStrings.ExecuteUpdateOverJsonIsNotSupported(complexType.DisplayName()));
+ }
+
if (TranslateSetterValueSelector(source, valueSelector, shaper.Type) is not Expression translatedValueSelector
|| !TryProcessComplexType(shaper, translatedValueSelector))
{
@@ -208,6 +213,9 @@ bool IsColumnOnSameTable(ColumnExpression column, LambdaExpression propertySelec
return true;
}
+ // Recursively processes the complex types and all complex types referenced by it, adding setters fo all (non-complex)
+ // properties.
+ // Note that this only supports table splitting (where all columns are flattened to the table), but not JSON complex types (#28766).
bool TryProcessComplexType(StructuralTypeShaperExpression shaperExpression, Expression valueExpression)
{
if (shaperExpression.StructuralType is not IComplexType complexType
@@ -241,7 +249,12 @@ bool TryProcessComplexType(StructuralTypeShaperExpression shaperExpression, Expr
// duplicated for every property on the complex type.
// TODO: Make this work by using a common table expression (CTE)
- var nestedShaperExpression = projection.BindComplexProperty(complexProperty);
+ if (complexProperty.ComplexType.IsMappedToJson())
+ {
+ throw new InvalidOperationException(RelationalStrings.ExecuteUpdateOverJsonIsNotSupported(complexProperty.ComplexType.DisplayName()));
+ }
+
+ var nestedShaperExpression = (StructuralTypeShaperExpression)projection.BindComplexProperty(complexProperty);
var nestedValueExpression = CreateComplexPropertyAccessExpression(valueExpression, complexProperty);
if (!TryProcessComplexType(nestedShaperExpression, nestedValueExpression))
{
diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
index 18bb222f2f1..48f63e6c20c 100644
--- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
@@ -262,15 +262,31 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
///
protected override ShapedQueryExpression? TranslateMemberAccess(Expression source, MemberIdentity member)
{
- // Attempt to translate access into a primitive collection property (i.e. array column)
- if (_sqlTranslator.TryBindMember(_sqlTranslator.Visit(source), member, out var translatedExpression, out var property)
- && property is IProperty { IsPrimitiveCollection: true } regularProperty
- && translatedExpression is SqlExpression sqlExpression
- && TranslatePrimitiveCollection(
- sqlExpression, regularProperty, _sqlAliasManager.GenerateTableAlias(GenerateTableAlias(sqlExpression))) is
- { } primitiveCollectionTranslation)
+ // Attempt to translate access into a primitive or complex collection property (i.e. array column)
+ if (_sqlTranslator.TryBindMember(_sqlTranslator.Visit(source), member, out var translatedExpression, out var property))
{
- return primitiveCollectionTranslation;
+ switch (property)
+ {
+ case IProperty { IsPrimitiveCollection: true } scalarProperty
+ when translatedExpression is SqlExpression sqlExpression
+ && TranslatePrimitiveCollection(
+ sqlExpression,
+ scalarProperty,
+ _sqlAliasManager.GenerateTableAlias(GenerateTableAlias(sqlExpression))) is { } primitiveCollectionTranslation:
+ {
+ return primitiveCollectionTranslation;
+ }
+
+ case IComplexProperty { IsCollection: true, ComplexType: var complexType } complexProperty:
+ Check.DebugAssert(complexType.IsMappedToJson());
+
+ if (translatedExpression is not CollectionResultExpression { QueryExpression: JsonQueryExpression jsonQuery })
+ {
+ throw new UnreachableException();
+ }
+
+ return TransformJsonQueryToTable(jsonQuery);
+ }
}
return null;
@@ -1605,7 +1621,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
var newJsonQuery = jsonQueryExpression.BindCollectionElement(collectionIndexExpression);
var entityShaper = new RelationalStructuralTypeShaperExpression(
- jsonQueryExpression.EntityType,
+ jsonQueryExpression.StructuralType,
newJsonQuery,
nullable: true);
@@ -1781,7 +1797,7 @@ Expression ExpandOwnedNavigation(INavigation navigation)
if (TryGetJsonQueryExpression(shaper, out var jsonQueryExpression))
{
- var newJsonQueryExpression = jsonQueryExpression.BindNavigation(navigation);
+ var newJsonQueryExpression = jsonQueryExpression.BindRelationship(navigation);
Debug.Assert(!navigation.IsOnDependent, "JSON navigations should always be from principal do dependent");
diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs
index 419e8e7bbea..f795d137eb1 100644
--- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs
+++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs
@@ -962,10 +962,10 @@ static async Task InitializeReaderAsync(
[EntityFrameworkInternal]
public static TEntity? MaterializeJsonEntity(
QueryContext queryContext,
- object[] keyPropertyValues,
+ object[]? keyPropertyValues,
JsonReaderData? jsonReaderData,
bool nullable,
- Func shaper)
+ Func shaper)
where TEntity : class
{
if (jsonReaderData == null)
@@ -1007,10 +1007,10 @@ static async Task InitializeReaderAsync(
[EntityFrameworkInternal]
public static TResult? MaterializeJsonEntityCollection(
QueryContext queryContext,
- object[] keyPropertyValues,
+ object[]? keyPropertyValues,
JsonReaderData? jsonReaderData,
- INavigationBase navigation,
- Func innerShaper)
+ IPropertyBase relationship,
+ Func innerShaper)
where TEntity : class
{
if (jsonReaderData == null)
@@ -1033,18 +1033,23 @@ static async Task InitializeReaderAsync(
break;
}
- var collectionAccessor = navigation.GetCollectionAccessor();
+ var collectionAccessor = relationship.GetCollectionAccessor();
var result = (TResult)collectionAccessor!.Create();
- var newKeyPropertyValues = new object[keyPropertyValues.Length + 1];
- Array.Copy(keyPropertyValues, newKeyPropertyValues, keyPropertyValues.Length);
+ object[]? newKeyPropertyValues = null;
+
+ if (keyPropertyValues is not null)
+ {
+ newKeyPropertyValues = new object[keyPropertyValues.Length + 1];
+ Array.Copy(keyPropertyValues, newKeyPropertyValues, keyPropertyValues.Length);
+ }
tokenType = manager.MoveNext();
var i = 0;
while (tokenType != JsonTokenType.EndArray)
{
- newKeyPropertyValues[^1] = ++i;
+ newKeyPropertyValues?[^1] = ++i;
if (tokenType == JsonTokenType.StartObject)
{
@@ -1082,10 +1087,10 @@ static async Task InitializeReaderAsync(
[EntityFrameworkInternal]
public static void IncludeJsonEntityReference(
QueryContext queryContext,
- object[] keyPropertyValues,
+ object[]? keyPropertyValues,
JsonReaderData? jsonReaderData,
TIncludingEntity entity,
- Func innerShaper,
+ Func innerShaper,
Action fixup,
bool trackingQuery)
where TIncludingEntity : class
@@ -1126,10 +1131,10 @@ public static void IncludeJsonEntityReference
[EntityFrameworkInternal]
public static void IncludeJsonEntityCollection(
QueryContext queryContext,
- object[] keyPropertyValues,
+ object[]? keyPropertyValues,
JsonReaderData? jsonReaderData,
TIncludingEntity entity,
- Func innerShaper,
+ Func innerShaper,
Action getOrCreateCollectionObject,
Action fixup,
bool trackingQuery)
@@ -1156,15 +1161,20 @@ public static void IncludeJsonEntityCollection
/// States to convert code to data reader read
///
- private readonly Dictionary> _materializationContextBindings = new();
+ private readonly Dictionary> _materializationContextBindings = new();
private readonly Dictionary _entityTypeIdentifyingExpressionInfo = new();
private readonly Dictionary _singleEntityTypeDiscriminatorValues = new();
@@ -490,10 +490,12 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
if (newExpression.Arguments[0] is ProjectionBindingExpression projectionBindingExpression)
{
- var projectionIndex = GetProjectionIndex(projectionBindingExpression);
- var propertyMap = projectionIndex is IDictionary
- ? (IDictionary)projectionIndex
- : ((QueryableJsonProjectionInfo)projectionIndex).PropertyIndexMap;
+ var propertyMap = GetProjectionIndex(projectionBindingExpression) switch
+ {
+ IDictionary p => p,
+ QueryableJsonProjectionInfo pi => pi.PropertyIndexMap,
+ _ => throw new UnreachableException()
+ };
_materializationContextBindings[parameterExpression] = propertyMap;
_entityTypeIdentifyingExpressionInfo[parameterExpression] =
@@ -585,109 +587,139 @@ protected override Expression VisitExtension(Expression extensionExpression)
// by creating every entity every time we guarantee this doesn't happen
if (!_isTracking || !_variableShaperMapping.TryGetValue(projectionBindingExpression, out var accessor))
{
- if (GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonProjectionInfo)
+ switch (GetProjectionIndex(projectionBindingExpression))
{
- Check.DebugAssert(shaper.StructuralType is IEntityType, "JsonProjectionInfo over a complex type");
- var entityType = (IEntityType)shaper.StructuralType;
-
- if (_isTracking)
+ case JsonProjectionInfo jsonProjectionInfo:
{
- throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner);
- }
-
- // json entity at the root
- var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess(
- jsonProjectionInfo,
- entityType,
- isCollection: false);
+ if (_isTracking)
+ {
+ // TODO: Update
+ throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner);
+ }
- var shaperResult = CreateJsonShapers(
- entityType,
- shaper.IsNullable,
- jsonReaderDataVariable,
- keyValuesParameter,
- parentEntityExpression: null,
- navigation: null);
+ // json entity at the root
+ var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess(
+ jsonProjectionInfo,
+ shaper.StructuralType,
+ isCollection: false);
- var visitedShaperResult = Visit(shaperResult);
- var visitedShaperResultParameter = Parameter(visitedShaperResult.Type);
- _variables.Add(visitedShaperResultParameter);
- _jsonEntityExpressions.Add(Assign(visitedShaperResultParameter, visitedShaperResult));
+ var shaperResult = CreateJsonShapers(
+ shaper.StructuralType,
+ shaper.IsNullable,
+ jsonReaderDataVariable,
+ keyValuesParameter,
+ containerEntityExpression: null,
+ relationship: null);
- accessor = CompensateForCollectionMaterialization(
- visitedShaperResultParameter,
- shaper.Type);
- }
- else if (GetProjectionIndex(projectionBindingExpression) is QueryableJsonProjectionInfo
- queryableJsonEntityProjectionInfo)
- {
- if (_isTracking)
- {
- throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner);
+ var visitedShaperResult = Visit(shaperResult);
+ var visitedShaperResultParameter = Parameter(visitedShaperResult.Type);
+ _variables.Add(visitedShaperResultParameter);
+ _jsonEntityExpressions.Add(Assign(visitedShaperResultParameter, visitedShaperResult));
+
+ accessor = CompensateForCollectionMaterialization(
+ visitedShaperResultParameter,
+ shaper.Type);
+ break;
}
- // json entity converted to query root and projected
- var entityParameter = Parameter(shaper.Type);
- _variables.Add(entityParameter);
- var entityMaterializationExpression = (BlockExpression)_parentVisitor.InjectEntityMaterializers(shaper);
+ case QueryableJsonProjectionInfo queryableJsonEntityProjectionInfo:
+ {
+ if (_isTracking)
+ {
+ throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner);
+ }
- var mappedProperties = queryableJsonEntityProjectionInfo.PropertyIndexMap.Keys.ToList();
- var rewrittenEntityMaterializationExpression = new QueryableJsonEntityMaterializerRewriter(mappedProperties)
- .Rewrite(entityMaterializationExpression);
+ // json entity converted to query root and projected
+ var entityParameter = Parameter(shaper.Type);
+ _variables.Add(entityParameter);
+ var entityMaterializationExpression = (BlockExpression)_parentVisitor.InjectStructuralTypeMaterializers(shaper);
- var visitedEntityMaterializationExpression = Visit(rewrittenEntityMaterializationExpression);
- _expressions.Add(Assign(entityParameter, visitedEntityMaterializationExpression));
+ var mappedProperties = queryableJsonEntityProjectionInfo.PropertyIndexMap.Keys.ToList();
+ Check.DebugAssert(mappedProperties.All(p => p is IProperty));
+ var rewrittenEntityMaterializationExpression = new QueryableJsonEntityMaterializerRewriter(mappedProperties)
+ .Rewrite(entityMaterializationExpression);
- foreach (var childProjectionInfo in queryableJsonEntityProjectionInfo.ChildrenProjectionInfo)
- {
- var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess(
- childProjectionInfo.JsonProjectionInfo,
- childProjectionInfo.Navigation.TargetEntityType,
- childProjectionInfo.Navigation.IsCollection);
+ var visitedEntityMaterializationExpression = Visit(rewrittenEntityMaterializationExpression);
+ _expressions.Add(Assign(entityParameter, visitedEntityMaterializationExpression));
- var shaperResult = CreateJsonShapers(
- childProjectionInfo.Navigation.TargetEntityType,
- nullable: true,
- jsonReaderDataVariable,
- keyValuesParameter,
- parentEntityExpression: entityParameter,
- navigation: childProjectionInfo.Navigation);
+ foreach (var childProjectionInfo in queryableJsonEntityProjectionInfo.ChildrenProjectionInfo)
+ {
+ var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess(
+ childProjectionInfo.JsonProjectionInfo,
+ childProjectionInfo.Navigation.TargetEntityType,
+ childProjectionInfo.Navigation.IsCollection);
+
+ var shaperResult = CreateJsonShapers(
+ childProjectionInfo.Navigation.TargetEntityType,
+ nullable: true,
+ jsonReaderDataVariable,
+ keyValuesParameter,
+ containerEntityExpression: entityParameter,
+ childProjectionInfo.Navigation);
+
+ var visitedShaperResult = Visit(shaperResult);
+
+ _includeExpressions.Add(visitedShaperResult);
+ }
- var visitedShaperResult = Visit(shaperResult);
+ accessor = CompensateForCollectionMaterialization(
+ entityParameter,
+ shaper.Type);
- _includeExpressions.Add(visitedShaperResult);
+ break;
}
- accessor = CompensateForCollectionMaterialization(
- entityParameter,
- shaper.Type);
- }
- else
- {
- var entityParameter = Parameter(shaper.Type, "entity");
- _variables.Add(entityParameter);
- if (shaper.StructuralType is IEntityType entityType
- && entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy)
+ default:
{
- var concreteTypes = 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)
+ var entityParameter = Parameter(shaper.Type, "entity");
+ _variables.Add(entityParameter);
+ if (shaper.StructuralType is IEntityType entityType
+ && entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy)
{
- _singleEntityTypeDiscriminatorValues[
- projectionBindingExpression]
- = concreteTypes[0].ShortName();
+ var concreteTypes = 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] = concreteTypes[0].ShortName();
+ }
}
- }
- var entityMaterializationExpression = _parentVisitor.InjectEntityMaterializers(shaper);
- entityMaterializationExpression = Visit(entityMaterializationExpression);
+ var entityMaterializationExpression = _parentVisitor.InjectStructuralTypeMaterializers(shaper);
+ entityMaterializationExpression = Visit(entityMaterializationExpression);
- _expressions.Add(Assign(entityParameter, entityMaterializationExpression));
+ _expressions.Add(Assign(entityParameter, entityMaterializationExpression));
+
+ accessor = CompensateForCollectionMaterialization(
+ entityParameter,
+ shaper.Type);
+
+ if (GetProjectionIndex(projectionBindingExpression) is IDictionary propertyMap)
+ {
+ foreach (var (property, projectionIndex) in propertyMap)
+ {
+ if (property is IComplexProperty { ComplexType: var complexType } complexProperty
+ && complexType.IsMappedToJson())
+ {
+ var jsonReaderDataVariable = GenerateJsonReader(projectionIndex, complexType);
+
+ var shaperResult = CreateJsonShapers(
+ complexType,
+ nullable: true, // TODO
+ jsonReaderDataVariable,
+ keyValuesParameter: null!, // TODO
+ containerEntityExpression: entityParameter,
+ relationship: complexProperty);
+
+ var visitedShaperResult = Visit(shaperResult);
+
+ _includeExpressions.Add(visitedShaperResult);
+ }
+ }
+ }
- accessor = CompensateForCollectionMaterialization(
- entityParameter,
- shaper.Type);
+ break;
+ }
}
if (_isTracking)
@@ -716,45 +748,56 @@ protected override Expression VisitExtension(Expression extensionExpression)
}
}
- var entityMaterializationExpression = _parentVisitor.InjectEntityMaterializers(shaper);
+ var entityMaterializationExpression = _parentVisitor.InjectStructuralTypeMaterializers(shaper);
entityMaterializationExpression = Visit(entityMaterializationExpression);
return entityMaterializationExpression;
}
- case CollectionResultExpression { Navigation: INavigation navigation } collectionResultExpression
- when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression)
- is JsonProjectionInfo jsonProjectionInfo:
+ case CollectionResultExpression
+ {
+ QueryExpression: ProjectionBindingExpression projectionBindingExpression,
+ Relationship: IPropertyBase relationship,
+ } collectionResult
+ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonProjectionInfo:
{
if (_isTracking)
{
throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner);
}
+ var relatedStructuralType = relationship switch
+ {
+ IComplexProperty complexProperty => (ITypeBase)complexProperty.ComplexType,
+ INavigation navigation => navigation.TargetEntityType,
+
+ _ => throw new UnreachableException()
+ };
+
// json entity collection at the root
var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess(
jsonProjectionInfo,
- navigation.TargetEntityType,
+ relatedStructuralType,
isCollection: true);
var shaperResult = CreateJsonShapers(
- navigation.TargetEntityType,
+ relatedStructuralType,
nullable: true,
jsonReaderDataVariable,
keyValuesParameter,
- parentEntityExpression: null,
- navigation: navigation);
+ containerEntityExpression: null,
+ relationship);
var visitedShaperResult = Visit(shaperResult);
- var jsonCollectionParameter = Parameter(collectionResultExpression.Type);
+ var jsonCollectionParameter = Parameter(collectionResult.Type);
_variables.Add(jsonCollectionParameter);
_jsonEntityExpressions.Add(Assign(jsonCollectionParameter, visitedShaperResult));
return CompensateForCollectionMaterialization(
jsonCollectionParameter,
- collectionResultExpression.Type);
+ collectionResult.Type);
}
case ProjectionBindingExpression projectionBindingExpression
@@ -898,7 +941,7 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression)
outerIdentifierLambdaCompiled,
_parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant(
navigation,
- LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(navigation),
+ LiftableConstantExpressionHelpers.BuildRelationshipAccessLambda(navigation),
navigation.Name + "Navigation",
typeof(INavigationBase)),
navigation.IsShadowProperty()
@@ -962,7 +1005,7 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression)
innerShaper,
_parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant(
inverseNavigation,
- LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(inverseNavigation),
+ LiftableConstantExpressionHelpers.BuildRelationshipAccessLambda(inverseNavigation),
(inverseNavigation?.Name ?? "null") + "InverseNavigation",
typeof(INavigationBase)),
GenerateFixup(includingEntityClrType, relatedEntityClrType, navigation, inverseNavigation),
@@ -1036,7 +1079,7 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression)
parentIdentifierLambdaCompiled,
_parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant(
navigation,
- LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(navigation),
+ LiftableConstantExpressionHelpers.BuildRelationshipAccessLambda(navigation),
navigation.Name + "Navigation",
typeof(INavigationBase)),
_parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant(
@@ -1084,7 +1127,7 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression)
: typeof(Action)),
_parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant(
inverseNavigation,
- LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(inverseNavigation),
+ LiftableConstantExpressionHelpers.BuildRelationshipAccessLambda(inverseNavigation),
(inverseNavigation?.Name ?? "null") + "InverseNavigation",
typeof(INavigationBase)),
GenerateFixup(includingEntityClrType, relatedEntityClrType, navigation, inverseNavigation),
@@ -1093,10 +1136,9 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression)
else
{
var projectionBindingExpression = (includeExpression.NavigationExpression as CollectionResultExpression)
- ?.ProjectionBindingExpression
+ ?.QueryExpression as ProjectionBindingExpression
?? (includeExpression.NavigationExpression as RelationalStructuralTypeShaperExpression)
- ?.ValueBufferExpression as
- ProjectionBindingExpression;
+ ?.ValueBufferExpression as ProjectionBindingExpression;
// json include case
if (projectionBindingExpression != null
@@ -1112,8 +1154,8 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression)
nullable: true,
jsonReaderDataVariable,
keyValuesParameter,
- parentEntityExpression: entity,
- navigation: (INavigation)includeExpression.Navigation);
+ containerEntityExpression: entity,
+ (INavigation)includeExpression.Navigation);
var visitedShaperResult = Visit(shaperResult);
@@ -1141,12 +1183,12 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression)
navigationExpression,
_parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant(
navigation,
- LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(navigation),
+ LiftableConstantExpressionHelpers.BuildRelationshipAccessLambda(navigation),
navigation.Name + "Navigation",
typeof(INavigation)),
_parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant(
inverseNavigation,
- LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(inverseNavigation),
+ LiftableConstantExpressionHelpers.BuildRelationshipAccessLambda(inverseNavigation),
(inverseNavigation?.Name ?? "null") + "InverseNavigation",
typeof(INavigation)),
GenerateFixup(includingType, relatedEntityType, navigation, inverseNavigation),
@@ -1454,8 +1496,8 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
var property = methodCallExpression.Arguments[2].GetConstantValue();
var mappingParameter = (ParameterExpression)((MethodCallExpression)methodCallExpression.Arguments[0]).Object!;
- if (_jsonMaterializationContextToJsonReaderDataAndKeyValuesParameterMapping.TryGetValue(
- mappingParameter, out var mappedParameter))
+ if (_jsonMaterializationContextToJsonReaderDataAndKeyValuesParameterMapping
+ .TryGetValue(mappingParameter, out var mappedParameter))
{
var (jsonReaderDataParameter, keyPropertyValuesParameter) = mappedParameter;
@@ -1518,16 +1560,17 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
}
private Expression CreateJsonShapers(
- IEntityType entityType,
+ ITypeBase structuralType,
bool nullable,
ParameterExpression jsonReaderDataParameter,
- ParameterExpression keyValuesParameter,
- Expression? parentEntityExpression,
- INavigation? navigation)
+ Expression? keyValuesParameter,
+ Expression? containerEntityExpression,
+ IPropertyBase? relationship)
{
var jsonReaderDataShaperLambdaParameter = Parameter(typeof(JsonReaderData));
// TODO: Use ISnapshot instead #26544
var keyValuesShaperLambdaParameter = Parameter(typeof(object[]));
+ keyValuesParameter ??= Constant(null, typeof(object[])); // For complex types
var shaperBlockVariables = new List();
var shaperBlockExpressions = new List();
@@ -1536,47 +1579,72 @@ private Expression CreateJsonShapers(
_jsonValueBufferToJsonReaderDataAndKeyValuesParameterMapping[valueBufferParameter] =
(jsonReaderDataShaperLambdaParameter, keyValuesShaperLambdaParameter);
- var entityShaperExpression = new RelationalStructuralTypeShaperExpression(
- entityType,
+ var structuralTypeShaperExpression = new RelationalStructuralTypeShaperExpression(
+ structuralType,
valueBufferParameter,
nullable);
- var entityShaperMaterializer = (BlockExpression)_parentVisitor.InjectEntityMaterializers(entityShaperExpression);
+ var structuralTypeShaperMaterializer =
+ (BlockExpression)_parentVisitor.InjectStructuralTypeMaterializers(structuralTypeShaperExpression);
var innerShapersMap = new Dictionary();
var innerFixupMap = new Dictionary();
var trackingInnerFixupMap = new Dictionary();
- foreach (var ownedNavigation in entityType.GetNavigations().Where(
- n => n.TargetEntityType.IsMappedToJson() && n.ForeignKey.IsOwnership && n == n.ForeignKey.PrincipalToDependent))
+
+ // Go over all relationships (complex properties and navigations - if we're an (owned) entity), which represent JSON
+ // nested types; generate shapers and fixup to wire the materialized related instance into the parent's property.
+ // Note that we need to build entity shapers and fixup separately; we don't know the order in which data comes, so
+ // we need to read through everything before we can do fixup safely
+ IEnumerable nestedRelationships = structuralType.GetComplexProperties();
+
+ if (structuralType is IEntityType entityType)
{
- Debug.Assert(!ownedNavigation.IsOnDependent, "JSON navigations should always be from principal do dependent");
+ nestedRelationships = nestedRelationships.Concat(
+ entityType.GetNavigations()
+ .Where(n => n.TargetEntityType.IsMappedToJson() && n.ForeignKey.IsOwnership && n == n.ForeignKey.PrincipalToDependent));
+ }
+
+ foreach (var nestedRelationship in nestedRelationships)
+ {
+ Check.DebugAssert(
+ nestedRelationship is not INavigation ownedNavigation || !ownedNavigation.IsOnDependent,
+ "JSON navigations should always be from principal do dependent");
+
+ Check.DebugAssert(
+ nestedRelationship is not IComplexProperty { ComplexType: var complexType } || complexType.IsMappedToJson(),
+ "Non-JSON complex type within JSON complex type");
+
+ var (relatedStructuralType, inverseNavigation, isRelationshipNullable) = nestedRelationship switch
+ {
+ INavigation n => ((ITypeBase)n.TargetEntityType, n.Inverse, !n.ForeignKey.IsRequiredDependent),
+ IComplexProperty cp => (cp.ComplexType, null, cp.IsNullable),
+
+ _ => throw new UnreachableException()
+ };
- // we need to build entity shapers and fixup separately
- // we don't know the order in which data comes, so we need to read through everything
- // before we can do fixup safely
var innerShaper = CreateJsonShapers(
- ownedNavigation.TargetEntityType,
- nullable || !ownedNavigation.ForeignKey.IsRequiredDependent,
+ relatedStructuralType,
+ nullable || isRelationshipNullable,
jsonReaderDataShaperLambdaParameter,
keyValuesShaperLambdaParameter,
- parentEntityExpression: null,
- navigation: ownedNavigation);
+ containerEntityExpression: null,
+ nestedRelationship);
- var navigationJsonPropertyName = ownedNavigation.TargetEntityType.GetJsonPropertyName()!;
+ var navigationJsonPropertyName = relatedStructuralType.GetJsonPropertyName()!;
innerShapersMap[navigationJsonPropertyName] = innerShaper;
- if (ownedNavigation.IsCollection)
+ if (nestedRelationship.IsCollection)
{
- var shaperEntityParameter = Parameter(ownedNavigation.DeclaringEntityType.ClrType);
- var ownedNavigationType = ownedNavigation.GetMemberInfo(forMaterialization: true, forSet: true).GetMemberType();
+ var shaperEntityParameter = Parameter(structuralType.ClrType);
+ var ownedNavigationType = nestedRelationship.GetMemberInfo(forMaterialization: true, forSet: true).GetMemberType();
var shaperCollectionParameter = Parameter(ownedNavigationType);
var expressions = new List();
var expressionsForTracking = new List();
- if (!ownedNavigation.IsShadowProperty())
+ if (!nestedRelationship.IsShadowProperty())
{
expressions.Add(
- shaperEntityParameter.MakeMemberAccess(ownedNavigation.GetMemberInfo(forMaterialization: true, forSet: true))
+ shaperEntityParameter.MakeMemberAccess(nestedRelationship.GetMemberInfo(forMaterialization: true, forSet: true))
.Assign(shaperCollectionParameter));
expressionsForTracking.Add(
@@ -1588,12 +1656,11 @@ private Expression CreateJsonShapers(
typeof(ShaperProcessingExpressionVisitor).GetMethod(nameof(Any))!,
shaperCollectionParameter))),
shaperEntityParameter
- .MakeMemberAccess(ownedNavigation.GetMemberInfo(forMaterialization: true, forSet: true))
+ .MakeMemberAccess(nestedRelationship.GetMemberInfo(forMaterialization: true, forSet: true))
.Assign(shaperCollectionParameter)));
}
- if (ownedNavigation.Inverse is INavigation inverseNavigation
- && !inverseNavigation.IsShadowProperty())
+ if (inverseNavigation is not null && !inverseNavigation.IsShadowProperty())
{
var innerFixupCollectionElementParameter = Parameter(inverseNavigation.DeclaringEntityType.ClrType);
var innerFixupParentParameter = Parameter(inverseNavigation.TargetEntityType.ClrType);
@@ -1601,7 +1668,7 @@ private Expression CreateJsonShapers(
var elementFixup = Lambda(
Block(
typeof(void),
- AssignReferenceNavigation(
+ AssignReferenceRelationship(
innerFixupCollectionElementParameter,
innerFixupParentParameter,
inverseNavigation)),
@@ -1636,27 +1703,27 @@ private Expression CreateJsonShapers(
else
{
var fixup = GenerateReferenceFixupForJson(
- ownedNavigation.DeclaringEntityType.ClrType,
- ownedNavigation.TargetEntityType.ClrType,
- ownedNavigation,
- ownedNavigation.Inverse);
+ structuralType.ClrType,
+ relatedStructuralType.ClrType,
+ nestedRelationship,
+ inverseNavigation);
innerFixupMap[navigationJsonPropertyName] = fixup;
}
}
var rewrittenEntityShaperMaterializer = new JsonEntityMaterializerRewriter(
- entityType,
+ structuralType,
_queryStateManager,
jsonReaderDataShaperLambdaParameter,
innerShapersMap,
innerFixupMap,
trackingInnerFixupMap,
_queryLogger,
- _parentVisitor.Dependencies.LiftableConstantFactory).Rewrite(entityShaperMaterializer);
+ _parentVisitor.Dependencies.LiftableConstantFactory).Rewrite(structuralTypeShaperMaterializer);
var entityShaperMaterializerVariable = Variable(
- entityShaperMaterializer.Type,
+ structuralTypeShaperMaterializer.Type,
"entityShaperMaterializer");
shaperBlockVariables.Add(entityShaperMaterializerVariable);
@@ -1672,57 +1739,57 @@ private Expression CreateJsonShapers(
keyValuesShaperLambdaParameter,
jsonReaderDataShaperLambdaParameter);
- if (parentEntityExpression != null)
+ if (containerEntityExpression is not null)
{
// this happens only on top level when we project owner entity in this case we can do fixup as part of generating materializer
// (since we are guaranteed that the parent already exists) - for nested JSON materialization we need to do fixup at the end
// because we are streaming the data and don't know if we get the parent json object before the child
// (in case parent ctor takes some parameters and they are read as last thing in the JSON)
- Check.DebugAssert(navigation != null, "Navigation shouldn't be null when including.");
+ Check.DebugAssert(relationship is not null, "relationship shouldn't be null when including.");
+
+ var declaringClrType = relationship.DeclaringType.ClrType;
+ var relatedClrType = relationship switch
+ {
+ INavigation n => n.TargetEntityType.ClrType,
+ IComplexProperty cp => cp.ComplexType.ClrType,
+ _ => throw new InvalidOperationException("Unsupported type for JSON materialization.")
+ };
var fixup = GenerateFixup(
- navigation.DeclaringEntityType.ClrType,
- navigation.TargetEntityType.ClrType,
- navigation,
- navigation.Inverse);
+ relationship.DeclaringType.ClrType,
+ relatedClrType,
+ relationship,
+ relationship is INavigation navigation ? navigation.Inverse : null);
- // inheritance scenario - navigation defined on derived
- var includingEntityExpression = parentEntityExpression.Type != navigation.DeclaringEntityType.ClrType
- ? Convert(parentEntityExpression, navigation.DeclaringEntityType.ClrType)
- : parentEntityExpression;
+ // inheritance scenario - navigation/complex property defined on derived
+ var includingEntityExpression = containerEntityExpression.Type != declaringClrType
+ ? Convert(containerEntityExpression, declaringClrType)
+ : containerEntityExpression;
- if (navigation.IsCollection)
+ if (relationship.IsCollection)
{
var includeJsonEntityCollectionMethodCall =
Call(
- IncludeJsonEntityCollectionMethodInfo.MakeGenericMethod(
- navigation.DeclaringEntityType.ClrType,
- navigation.TargetEntityType.ClrType),
+ IncludeJsonEntityCollectionMethodInfo.MakeGenericMethod(declaringClrType, relatedClrType),
QueryCompilationContext.QueryContextParameter,
keyValuesParameter,
jsonReaderDataParameter,
includingEntityExpression,
shaperLambda,
- GetOrCreateCollectionObjectLambda(
- navigation.DeclaringEntityType.ClrType,
- navigation),
+ GetOrCreateCollectionObjectLambda(declaringClrType, relationship),
fixup,
Constant(_isTracking));
- return navigation.DeclaringEntityType.ClrType.IsAssignableFrom(parentEntityExpression.Type)
+ return declaringClrType.IsAssignableFrom(containerEntityExpression.Type)
? includeJsonEntityCollectionMethodCall
: IfThen(
- TypeIs(
- parentEntityExpression,
- navigation.DeclaringEntityType.ClrType),
+ TypeIs(containerEntityExpression, declaringClrType),
includeJsonEntityCollectionMethodCall);
}
var includeJsonEntityReferenceMethodCall =
Call(
- IncludeJsonEntityReferenceMethodInfo.MakeGenericMethod(
- navigation.DeclaringEntityType.ClrType,
- navigation.TargetEntityType.ClrType),
+ IncludeJsonEntityReferenceMethodInfo.MakeGenericMethod(declaringClrType, relatedClrType),
QueryCompilationContext.QueryContextParameter,
keyValuesParameter,
jsonReaderDataParameter,
@@ -1731,38 +1798,41 @@ private Expression CreateJsonShapers(
fixup,
Constant(_isTracking));
- return navigation.DeclaringEntityType.ClrType.IsAssignableFrom(parentEntityExpression.Type)
+ return declaringClrType.IsAssignableFrom(containerEntityExpression.Type)
? includeJsonEntityReferenceMethodCall
: IfThen(
- TypeIs(
- parentEntityExpression,
- navigation.DeclaringEntityType.ClrType),
+ TypeIs(containerEntityExpression, declaringClrType),
includeJsonEntityReferenceMethodCall);
}
- if (navigation is { IsCollection: true })
+ if (relationship is { IsCollection: true })
{
- var collectionClrType = navigation.GetMemberInfo(forMaterialization: true, forSet: true).GetMemberType();
+ var collectionClrType = relationship.GetMemberInfo(forMaterialization: true, forSet: true).GetMemberType();
var materializeJsonEntityCollectionMethodCall =
Call(
MaterializeJsonEntityCollectionMethodInfo.MakeGenericMethod(
- navigation.TargetEntityType.ClrType,
+ relationship switch
+ {
+ INavigation n => n.TargetEntityType.ClrType,
+ IComplexProperty cp => cp.ComplexType.ClrType,
+ _ => throw new InvalidOperationException("Unsupported type for JSON materialization.")
+ },
collectionClrType),
QueryCompilationContext.QueryContextParameter,
keyValuesParameter,
jsonReaderDataParameter,
_parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant(
- navigation,
- LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(navigation),
- navigation.Name + "Navigation",
- typeof(INavigation)),
+ relationship,
+ LiftableConstantExpressionHelpers.BuildRelationshipAccessLambda(relationship),
+ relationship.Name + "Relationship",
+ typeof(IPropertyBase)),
shaperLambda);
return materializeJsonEntityCollectionMethodCall;
}
var materializedRootJsonEntity = Call(
- MaterializeJsonEntityMethodInfo.MakeGenericMethod(entityType.ClrType),
+ MaterializeJsonEntityMethodInfo.MakeGenericMethod(structuralType.ClrType),
QueryCompilationContext.QueryContextParameter,
keyValuesParameter,
jsonReaderDataParameter,
@@ -1773,7 +1843,7 @@ private Expression CreateJsonShapers(
}
private sealed class JsonEntityMaterializerRewriter(
- IEntityType entityType,
+ ITypeBase structuralType,
bool queryStateManager,
ParameterExpression jsonReaderDataParameter,
IDictionary innerShapersMap,
@@ -1798,7 +1868,7 @@ public BlockExpression Rewrite(BlockExpression jsonEntityShaperMaterializer)
protected override Expression VisitSwitch(SwitchExpression switchExpression)
{
- if (switchExpression.SwitchValue.Type == typeof(IEntityType)
+ if (switchExpression.SwitchValue.Type.IsAssignableTo(typeof(ITypeBase))
&& switchExpression is
{
Cases:
@@ -1809,10 +1879,10 @@ protected override Expression VisitSwitch(SwitchExpression switchExpression)
}
]
}
- && onlyValueExpression.GetConstantValue