@@ -2077,20 +2077,21 @@ void HandleStructuralTypeProjection(
20772077 projection1 . StructuralType . DisplayName ( ) , projection2 . StructuralType . DisplayName ( ) ) ) ;
20782078 }
20792079
2080- var propertyExpressions = new Dictionary < IProperty , ColumnExpression > ( ) ;
2081-
2082- ProcessStructuralType ( projection1 , projection2 ) ;
2080+ var resultProjection = ProcessStructuralType ( projection1 , projection2 ) ;
2081+ _projectionMapping [ projectionMember ] = resultProjection ;
20832082
2084- void ProcessStructuralType (
2085- StructuralTypeProjectionExpression nestedProjection1 ,
2086- StructuralTypeProjectionExpression nestedProjection2 )
2083+ StructuralTypeProjectionExpression ProcessStructuralType (
2084+ StructuralTypeProjectionExpression structuralProjection1 ,
2085+ StructuralTypeProjectionExpression structuralProjection2 )
20872086 {
2088- var type = nestedProjection1 . StructuralType ;
2087+ var propertyExpressions = new Dictionary < IProperty , ColumnExpression > ( ) ;
2088+ var complexPropertyCache = new Dictionary < IComplexProperty , StructuralTypeShaperExpression > ( ) ;
2089+ var type = structuralProjection1 . StructuralType ;
20892090
20902091 foreach ( var property in type . GetAllPropertiesInHierarchy ( ) )
20912092 {
2092- var column1 = nestedProjection1 . BindProperty ( property ) ;
2093- var column2 = nestedProjection2 . BindProperty ( property ) ;
2093+ var column1 = structuralProjection1 . BindProperty ( property ) ;
2094+ var column2 = structuralProjection2 . BindProperty ( property ) ;
20942095 var alias = GenerateUniqueColumnAlias ( column1 . Name ) ;
20952096 var innerProjection = new ProjectionExpression ( column1 , alias ) ;
20962097 select1 . _projection . Add ( innerProjection ) ;
@@ -2127,7 +2128,7 @@ void ProcessStructuralType(
21272128 // If the top-level projection - not the current nested one - is a complex type and not an entity type, then add
21282129 // all its columns to the "otherExpressions" list (i.e. columns not part of a an entity primary key). This is
21292130 // the same as with a non-structural type projection.
2130- else if ( projection1 . StructuralType is IComplexType )
2131+ else if ( structuralProjection1 . StructuralType is IComplexType )
21312132 {
21322133 var outerTypeMapping = column1 . TypeMapping ?? column1 . TypeMapping ;
21332134 if ( outerTypeMapping == null )
@@ -2141,52 +2142,68 @@ void ProcessStructuralType(
21412142 }
21422143 }
21432144
2144- foreach ( var complexProperty in GetAllComplexPropertiesInHierarchy ( nestedProjection1 . StructuralType ) )
2145+ foreach ( var complexProperty in GetAllComplexPropertiesInHierarchy ( structuralProjection1 . StructuralType ) )
21452146 {
2146- ProcessStructuralType (
2147- ( StructuralTypeProjectionExpression ) nestedProjection1 . BindComplexProperty ( complexProperty ) . ValueBufferExpression ,
2148- ( StructuralTypeProjectionExpression ) nestedProjection2 . BindComplexProperty ( complexProperty ) . ValueBufferExpression ) ;
2149- }
2150- }
2147+ var complexPropertyShaper1 = structuralProjection1 . BindComplexProperty ( complexProperty ) ;
2148+ var complexPropertyShaper2 = structuralProjection2 . BindComplexProperty ( complexProperty ) ;
21512149
2152- Check . DebugAssert (
2153- projection1 . TableMap . Count == projection2 . TableMap . Count ,
2154- "Set operation over entity projections with different table map counts" ) ;
2155- Check . DebugAssert (
2156- projection1 . TableMap . Keys . All ( t => projection2 . TableMap . ContainsKey ( t ) ) ,
2157- "Set operation over entity projections with table map discrepancy" ) ;
2150+ var resultComplexProjection = ProcessStructuralType (
2151+ ( StructuralTypeProjectionExpression ) complexPropertyShaper1 . ValueBufferExpression ,
2152+ ( StructuralTypeProjectionExpression ) complexPropertyShaper2 . ValueBufferExpression ) ;
21582153
2159- var tableMap = projection1 . TableMap . ToDictionary ( kvp => kvp . Key , _ => setOperationAlias ) ;
2154+ var resultComplexShaper = new RelationalStructuralTypeShaperExpression (
2155+ complexProperty . ComplexType ,
2156+ resultComplexProjection ,
2157+ resultComplexProjection . IsNullable ) ;
21602158
2161- var discriminatorExpression = projection1 . DiscriminatorExpression ;
2162- if ( projection1 . DiscriminatorExpression != null
2163- && projection2 . DiscriminatorExpression != null )
2164- {
2165- var alias = GenerateUniqueColumnAlias ( DiscriminatorColumnAlias ) ;
2166- var innerProjection = new ProjectionExpression ( projection1 . DiscriminatorExpression , alias ) ;
2167- select1 . _projection . Add ( innerProjection ) ;
2168- select2 . _projection . Add ( new ProjectionExpression ( projection2 . DiscriminatorExpression , alias ) ) ;
2169- discriminatorExpression = CreateColumnExpression ( innerProjection , setOperationAlias ) ;
2170- }
2159+ complexPropertyCache [ complexProperty ] = resultComplexShaper ;
2160+ }
21712161
2172- var outerProjection = new StructuralTypeProjectionExpression (
2173- projection1 . StructuralType , propertyExpressions , tableMap , nullable : false , discriminatorExpression ) ;
2162+ Check . DebugAssert (
2163+ structuralProjection1 . TableMap . Count == structuralProjection2 . TableMap . Count ,
2164+ "Set operation over entity projections with different table map counts" ) ;
2165+ Check . DebugAssert (
2166+ structuralProjection1 . TableMap . Keys . All ( t => structuralProjection2 . TableMap . ContainsKey ( t ) ) ,
2167+ "Set operation over entity projections with table map discrepancy" ) ;
2168+ Check . DebugAssert (
2169+ structuralProjection1 . StructuralType == structuralProjection2 . StructuralType ,
2170+ "Set operation over entity projections with different structural types" ) ;
2171+ Check . DebugAssert (
2172+ structuralProjection1 . IsNullable == structuralProjection2 . IsNullable ,
2173+ "Set operation over entity projections with different nullabilities" ) ;
2174+
2175+ var tableMap = projection1 . TableMap . ToDictionary ( kvp => kvp . Key , _ => setOperationAlias ) ;
2176+
2177+ var discriminatorExpression = structuralProjection1 . DiscriminatorExpression ;
2178+ if ( structuralProjection1 . DiscriminatorExpression != null
2179+ && structuralProjection2 . DiscriminatorExpression != null )
2180+ {
2181+ var alias = GenerateUniqueColumnAlias ( DiscriminatorColumnAlias ) ;
2182+ var innerProjection = new ProjectionExpression ( structuralProjection1 . DiscriminatorExpression , alias ) ;
2183+ select1 . _projection . Add ( innerProjection ) ;
2184+ select2 . _projection . Add ( new ProjectionExpression ( structuralProjection2 . DiscriminatorExpression , alias ) ) ;
2185+ discriminatorExpression = CreateColumnExpression ( innerProjection , setOperationAlias ) ;
2186+ }
21742187
2175- if ( outerIdentifiers . Length > 0 && outerProjection is { StructuralType : IEntityType entityType } )
2176- {
2177- var primaryKey = entityType . FindPrimaryKey ( ) ;
2188+ var outerProjection = new StructuralTypeProjectionExpression (
2189+ structuralProjection1 . StructuralType , propertyExpressions , complexPropertyCache , tableMap , nullable : false , discriminatorExpression ) ;
21782190
2179- // We know that there are existing identifiers (see condition above); we know we must have a key since a keyless
2180- // entity type would have wiped the identifiers when generating the join.
2181- Check . DebugAssert ( primaryKey != null , "primary key is null." ) ;
2182- foreach ( var property in primaryKey . Properties )
2191+ if ( outerIdentifiers . Length > 0 && outerProjection is { StructuralType : IEntityType entityType } )
21832192 {
2184- entityProjectionIdentifiers . Add ( outerProjection . BindProperty ( property ) ) ;
2185- entityProjectionValueComparers . Add ( property . GetKeyValueComparer ( ) ) ;
2193+ var primaryKey = entityType . FindPrimaryKey ( ) ;
2194+
2195+ // We know that there are existing identifiers (see condition above); we know we must have a key since a keyless
2196+ // entity type would have wiped the identifiers when generating the join.
2197+ Check . DebugAssert ( primaryKey != null , "primary key is null." ) ;
2198+ foreach ( var property in primaryKey . Properties )
2199+ {
2200+ entityProjectionIdentifiers . Add ( outerProjection . BindProperty ( property ) ) ;
2201+ entityProjectionValueComparers . Add ( property . GetKeyValueComparer ( ) ) ;
2202+ }
21862203 }
2187- }
21882204
2189- _projectionMapping [ projectionMember ] = outerProjection ;
2205+ return outerProjection ;
2206+ }
21902207 }
21912208
21922209 string GenerateUniqueColumnAlias ( string baseAlias )
@@ -2560,10 +2577,10 @@ public static StructuralTypeShaperExpression GenerateComplexPropertyShaperExpres
25602577 // We do not support complex type splitting, so we will only ever have a single table/view mapping to it.
25612578 // See Issue #32853 and Issue #31248
25622579 var complexTypeTable = complexProperty . ComplexType . GetViewOrTableMappings ( ) . Single ( ) . Table ;
2563- if ( ! containerProjection . TableMap . TryGetValue ( complexTypeTable , out var tableReferenceExpression ) )
2580+ if ( ! containerProjection . TableMap . TryGetValue ( complexTypeTable , out var tableAlias ) )
25642581 {
25652582 complexTypeTable = complexProperty . ComplexType . GetDefaultMappings ( ) . Single ( ) . Table ;
2566- tableReferenceExpression = containerProjection . TableMap [ complexTypeTable ] ;
2583+ tableAlias = containerProjection . TableMap [ complexTypeTable ] ;
25672584 }
25682585 var isComplexTypeNullable = containerProjection . IsNullable || complexProperty . IsNullable ;
25692586
@@ -2581,14 +2598,14 @@ public static StructuralTypeShaperExpression GenerateComplexPropertyShaperExpres
25812598 // TODO: Reimplement EntityProjectionExpression via TableMap, and then use that here
25822599 var column = complexTypeTable . FindColumn ( property ) ! ;
25832600 propertyExpressionMap [ property ] = CreateColumnExpression (
2584- property , column , tableReferenceExpression , isComplexTypeNullable || column . IsNullable ) ;
2601+ property , column , tableAlias , isComplexTypeNullable || column . IsNullable ) ;
25852602 }
25862603
25872604 // The table map of the target complex type should only ever contains a single table (no table splitting).
25882605 // If the source is itself a complex type (nested complex type), its table map is already suitable and we can just pass it on.
25892606 var newTableMap = containerProjection . TableMap . Count == 1
25902607 ? containerProjection . TableMap
2591- : new Dictionary < ITableBase , string > { [ complexTypeTable ] = tableReferenceExpression } ;
2608+ : new Dictionary < ITableBase , string > { [ complexTypeTable ] = tableAlias } ;
25922609
25932610 Check . DebugAssert ( newTableMap . Single ( ) . Key == complexTypeTable , "Bad new table map" ) ;
25942611
@@ -3530,33 +3547,35 @@ StructuralTypeProjectionExpression LiftEntityProjectionFromSubquery(
35303547 string subqueryAlias )
35313548 {
35323549 var propertyExpressions = new Dictionary < IProperty , ColumnExpression > ( ) ;
3550+ var complexPropertyCache = new Dictionary < IComplexProperty , StructuralTypeShaperExpression > ( ) ;
35333551
3534- HandleTypeProjection ( projection ) ;
3535-
3536- void HandleTypeProjection ( StructuralTypeProjectionExpression typeProjection )
3552+ foreach ( var property in projection . StructuralType . GetAllPropertiesInHierarchy ( ) )
35373553 {
3538- foreach ( var property in typeProjection . StructuralType . GetAllPropertiesInHierarchy ( ) )
3554+ // json entity projection (i.e. JSON entity that was transformed into query root) may have synthesized keys
3555+ // but they don't correspond to any columns - we need to skip those
3556+ if ( projection is { StructuralType : IEntityType entityType }
3557+ && entityType . IsMappedToJson ( )
3558+ && property . IsOrdinalKeyProperty ( ) )
35393559 {
3540- // json entity projection (i.e. JSON entity that was transformed into query root) may have synthesized keys
3541- // but they don't correspond to any columns - we need to skip those
3542- if ( typeProjection is { StructuralType : IEntityType entityType }
3543- && entityType . IsMappedToJson ( )
3544- && property . IsOrdinalKeyProperty ( ) )
3545- {
3546- continue ;
3547- }
3548-
3549- var innerColumn = typeProjection . BindProperty ( property ) ;
3550- var outerColumn = subquery . GenerateOuterColumn ( subqueryAlias , innerColumn ) ;
3551- projectionMap [ innerColumn ] = outerColumn ;
3552- propertyExpressions [ property ] = outerColumn ;
3560+ continue ;
35533561 }
35543562
3555- foreach ( var complexProperty in GetAllComplexPropertiesInHierarchy ( typeProjection . StructuralType ) )
3556- {
3557- HandleTypeProjection (
3558- ( StructuralTypeProjectionExpression ) typeProjection . BindComplexProperty ( complexProperty ) . ValueBufferExpression ) ;
3559- }
3563+ var innerColumn = projection . BindProperty ( property ) ;
3564+ var outerColumn = subquery . GenerateOuterColumn ( subqueryAlias , innerColumn ) ;
3565+
3566+ projectionMap [ innerColumn ] = outerColumn ;
3567+ propertyExpressions [ property ] = outerColumn ;
3568+ }
3569+
3570+ foreach ( var complexProperty in GetAllComplexPropertiesInHierarchy ( projection . StructuralType ) )
3571+ {
3572+ var complexPropertyShaper = projection . BindComplexProperty ( complexProperty ) ;
3573+
3574+ var complexTypeProjectionExpression = LiftEntityProjectionFromSubquery (
3575+ ( StructuralTypeProjectionExpression ) complexPropertyShaper . ValueBufferExpression ,
3576+ subqueryAlias ) ;
3577+
3578+ complexPropertyCache [ complexProperty ] = complexPropertyShaper . Update ( complexTypeProjectionExpression ) ;
35603579 }
35613580
35623581 ColumnExpression ? discriminatorExpression = null ;
@@ -3570,7 +3589,7 @@ void HandleTypeProjection(StructuralTypeProjectionExpression typeProjection)
35703589 var tableMap = projection . TableMap . ToDictionary ( kvp => kvp . Key , _ => subqueryAlias ) ;
35713590
35723591 var newEntityProjection = new StructuralTypeProjectionExpression (
3573- projection . StructuralType , propertyExpressions , tableMap , nullable : false , discriminatorExpression ) ;
3592+ projection . StructuralType , propertyExpressions , complexPropertyCache , tableMap , nullable : false , discriminatorExpression ) ;
35743593
35753594 if ( projection . StructuralType is IEntityType entityType2 )
35763595 {
0 commit comments