@@ -17,7 +17,7 @@ namespace Microsoft.EntityFrameworkCore.Query;
17
17
public class EntityProjectionExpression : Expression
18
18
{
19
19
private readonly IReadOnlyDictionary < IProperty , ColumnExpression > _propertyExpressionMap ;
20
- private readonly Dictionary < INavigation , EntityShaperExpression > _ownedNavigationMap = new ( ) ;
20
+ private readonly Dictionary < INavigation , EntityShaperExpression > _ownedNavigationMap ;
21
21
22
22
/// <summary>
23
23
/// Creates a new instance of the <see cref="EntityProjectionExpression" /> class.
@@ -29,9 +29,23 @@ public EntityProjectionExpression(
29
29
IEntityType entityType ,
30
30
IReadOnlyDictionary < IProperty , ColumnExpression > propertyExpressionMap ,
31
31
SqlExpression ? discriminatorExpression = null )
32
+ : this (
33
+ entityType ,
34
+ propertyExpressionMap ,
35
+ new Dictionary < INavigation , EntityShaperExpression > ( ) ,
36
+ discriminatorExpression )
37
+ {
38
+ }
39
+
40
+ private EntityProjectionExpression (
41
+ IEntityType entityType ,
42
+ IReadOnlyDictionary < IProperty , ColumnExpression > propertyExpressionMap ,
43
+ Dictionary < INavigation , EntityShaperExpression > ownedNavigationMap ,
44
+ SqlExpression ? discriminatorExpression = null )
32
45
{
33
46
EntityType = entityType ;
34
47
_propertyExpressionMap = propertyExpressionMap ;
48
+ _ownedNavigationMap = ownedNavigationMap ;
35
49
DiscriminatorExpression = discriminatorExpression ;
36
50
}
37
51
@@ -69,8 +83,16 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
69
83
var discriminatorExpression = ( SqlExpression ? ) visitor . Visit ( DiscriminatorExpression ) ;
70
84
changed |= discriminatorExpression != DiscriminatorExpression ;
71
85
86
+ var ownedNavigationMap = new Dictionary < INavigation , EntityShaperExpression > ( ) ;
87
+ foreach ( var ( navigation , entityShaperExpression ) in _ownedNavigationMap )
88
+ {
89
+ var newExpression = ( EntityShaperExpression ) visitor . Visit ( entityShaperExpression ) ;
90
+ changed |= newExpression != entityShaperExpression ;
91
+ ownedNavigationMap [ navigation ] = newExpression ;
92
+ }
93
+
72
94
return changed
73
- ? new EntityProjectionExpression ( EntityType , propertyExpressionMap , discriminatorExpression )
95
+ ? new EntityProjectionExpression ( EntityType , propertyExpressionMap , ownedNavigationMap , discriminatorExpression )
74
96
: this ;
75
97
}
76
98
@@ -92,7 +114,65 @@ public virtual EntityProjectionExpression MakeNullable()
92
114
// if discriminator is column then we need to make it nullable
93
115
discriminatorExpression = ce . MakeNullable ( ) ;
94
116
}
95
- return new EntityProjectionExpression ( EntityType , propertyExpressionMap , discriminatorExpression ) ;
117
+
118
+ var primaryKeyProperties = GetMappedKeyProperties ( EntityType . FindPrimaryKey ( ) ! ) ;
119
+ var ownedNavigationMap = new Dictionary < INavigation , EntityShaperExpression > ( ) ;
120
+ foreach ( var ( navigation , shaper ) in _ownedNavigationMap )
121
+ {
122
+ if ( shaper . EntityType . IsMappedToJson ( ) )
123
+ {
124
+ // even if shaper is nullable, we need to make sure key property map contains nullable keys,
125
+ // if json entity itself is optional, the shaper would be null, but the PK of the owner entity would be non-nullable intially
126
+ Debug . Assert ( primaryKeyProperties != null , "Json entity type can't be keyless" ) ;
127
+
128
+ var jsonQueryExpression = ( JsonQueryExpression ) shaper . ValueBufferExpression ;
129
+ var ownedPrimaryKeyProperties = GetMappedKeyProperties ( shaper . EntityType . FindPrimaryKey ( ) ! ) ! ;
130
+ var nullableKeyPropertyMap = new Dictionary < IProperty , ColumnExpression > ( ) ;
131
+ for ( var i = 0 ; i < primaryKeyProperties . Count ; i ++ )
132
+ {
133
+ nullableKeyPropertyMap [ ownedPrimaryKeyProperties [ i ] ] = propertyExpressionMap [ primaryKeyProperties [ i ] ] ;
134
+ }
135
+
136
+ // reuse key columns from owner (that we just made nullable), so that the references are the same
137
+ var newJsonQueryExpression = jsonQueryExpression . MakeNullable ( nullableKeyPropertyMap ) ;
138
+ var newShaper = shaper . Update ( newJsonQueryExpression ) . MakeNullable ( ) ;
139
+ ownedNavigationMap [ navigation ] = newShaper ;
140
+ }
141
+ }
142
+
143
+ return new EntityProjectionExpression (
144
+ EntityType ,
145
+ propertyExpressionMap ,
146
+ ownedNavigationMap ,
147
+ discriminatorExpression ) ;
148
+
149
+ static IReadOnlyList < IProperty > ? GetMappedKeyProperties ( IKey ? key )
150
+ {
151
+ if ( key == null )
152
+ {
153
+ return null ;
154
+ }
155
+
156
+ if ( ! key . DeclaringEntityType . IsMappedToJson ( ) )
157
+ {
158
+ return key . Properties ;
159
+ }
160
+
161
+ // TODO: fix this once we enable json entity being owned by another owned non-json entity (issue #28441)
162
+
163
+ // for json collections we need to filter out the ordinal key as it's not mapped to any column
164
+ // there could be multiple of these in deeply nested structures,
165
+ // so we traverse to the outermost owner to see how many mapped keys there are
166
+ var currentEntity = key . DeclaringEntityType ;
167
+ while ( currentEntity . IsMappedToJson ( ) )
168
+ {
169
+ currentEntity = currentEntity . FindOwnership ( ) ! . PrincipalEntityType ;
170
+ }
171
+
172
+ var count = currentEntity . FindPrimaryKey ( ) ! . Properties . Count ;
173
+
174
+ return key . Properties . Take ( count ) . ToList ( ) ;
175
+ }
96
176
}
97
177
98
178
/// <summary>
@@ -119,6 +199,16 @@ public virtual EntityProjectionExpression UpdateEntityType(IEntityType derivedTy
119
199
}
120
200
}
121
201
202
+ var ownedNavigationMap = new Dictionary < INavigation , EntityShaperExpression > ( ) ;
203
+ foreach ( var ( navigation , entityShaperExpression ) in _ownedNavigationMap )
204
+ {
205
+ if ( derivedType . IsAssignableFrom ( navigation . DeclaringEntityType )
206
+ || navigation . DeclaringEntityType . IsAssignableFrom ( derivedType ) )
207
+ {
208
+ ownedNavigationMap [ navigation ] = entityShaperExpression ;
209
+ }
210
+ }
211
+
122
212
var discriminatorExpression = DiscriminatorExpression ;
123
213
if ( DiscriminatorExpression is CaseExpression caseExpression )
124
214
{
@@ -130,7 +220,7 @@ public virtual EntityProjectionExpression UpdateEntityType(IEntityType derivedTy
130
220
discriminatorExpression = caseExpression . Update ( operand : null , whenClauses , elseResult : null ) ;
131
221
}
132
222
133
- return new EntityProjectionExpression ( derivedType , propertyExpressionMap , discriminatorExpression ) ;
223
+ return new EntityProjectionExpression ( derivedType , propertyExpressionMap , ownedNavigationMap , discriminatorExpression ) ;
134
224
}
135
225
136
226
/// <summary>
0 commit comments