@@ -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 ( type is IComplexType )
21312132 {
21322133 var outerTypeMapping = column1 . TypeMapping ?? column1 . TypeMapping ;
21332134 if ( outerTypeMapping == null )
@@ -2141,52 +2142,62 @@ void ProcessStructuralType(
21412142 }
21422143 }
21432144
2144- foreach ( var complexProperty in GetAllComplexPropertiesInHierarchy ( nestedProjection1 . StructuralType ) )
2145+ foreach ( var complexProperty in GetAllComplexPropertiesInHierarchy ( type ) )
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" ) ;
21742168
2175- if ( outerIdentifiers . Length > 0 && outerProjection is { StructuralType : IEntityType entityType } )
2176- {
2177- var primaryKey = entityType . FindPrimaryKey ( ) ;
2169+ var tableMap = projection1 . TableMap . ToDictionary ( kvp => kvp . Key , _ => setOperationAlias ) ;
21782170
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 )
2171+ var discriminatorExpression = structuralProjection1 . DiscriminatorExpression ;
2172+ if ( structuralProjection1 . DiscriminatorExpression != null
2173+ && structuralProjection2 . DiscriminatorExpression != null )
21832174 {
2184- entityProjectionIdentifiers . Add ( outerProjection . BindProperty ( property ) ) ;
2185- entityProjectionValueComparers . Add ( property . GetKeyValueComparer ( ) ) ;
2175+ var alias = GenerateUniqueColumnAlias ( DiscriminatorColumnAlias ) ;
2176+ var innerProjection = new ProjectionExpression ( structuralProjection1 . DiscriminatorExpression , alias ) ;
2177+ select1 . _projection . Add ( innerProjection ) ;
2178+ select2 . _projection . Add ( new ProjectionExpression ( structuralProjection2 . DiscriminatorExpression , alias ) ) ;
2179+ discriminatorExpression = CreateColumnExpression ( innerProjection , setOperationAlias ) ;
21862180 }
2187- }
21882181
2189- _projectionMapping [ projectionMember ] = outerProjection ;
2182+ var outerProjection = new StructuralTypeProjectionExpression (
2183+ type , propertyExpressions , complexPropertyCache , tableMap , nullable : false , discriminatorExpression ) ;
2184+
2185+ if ( outerIdentifiers . Length > 0 && outerProjection is { StructuralType : IEntityType entityType } )
2186+ {
2187+ var primaryKey = entityType . FindPrimaryKey ( ) ;
2188+
2189+ // We know that there are existing identifiers (see condition above); we know we must have a key since a keyless
2190+ // entity type would have wiped the identifiers when generating the join.
2191+ Check . DebugAssert ( primaryKey != null , "primary key is null." ) ;
2192+ foreach ( var property in primaryKey . Properties )
2193+ {
2194+ entityProjectionIdentifiers . Add ( outerProjection . BindProperty ( property ) ) ;
2195+ entityProjectionValueComparers . Add ( property . GetKeyValueComparer ( ) ) ;
2196+ }
2197+ }
2198+
2199+ return outerProjection ;
2200+ }
21902201 }
21912202
21922203 string GenerateUniqueColumnAlias ( string baseAlias )
@@ -2560,10 +2571,10 @@ public static StructuralTypeShaperExpression GenerateComplexPropertyShaperExpres
25602571 // We do not support complex type splitting, so we will only ever have a single table/view mapping to it.
25612572 // See Issue #32853 and Issue #31248
25622573 var complexTypeTable = complexProperty . ComplexType . GetViewOrTableMappings ( ) . Single ( ) . Table ;
2563- if ( ! containerProjection . TableMap . TryGetValue ( complexTypeTable , out var tableReferenceExpression ) )
2574+ if ( ! containerProjection . TableMap . TryGetValue ( complexTypeTable , out var tableAlias ) )
25642575 {
25652576 complexTypeTable = complexProperty . ComplexType . GetDefaultMappings ( ) . Single ( ) . Table ;
2566- tableReferenceExpression = containerProjection . TableMap [ complexTypeTable ] ;
2577+ tableAlias = containerProjection . TableMap [ complexTypeTable ] ;
25672578 }
25682579 var isComplexTypeNullable = containerProjection . IsNullable || complexProperty . IsNullable ;
25692580
@@ -2581,14 +2592,14 @@ public static StructuralTypeShaperExpression GenerateComplexPropertyShaperExpres
25812592 // TODO: Reimplement EntityProjectionExpression via TableMap, and then use that here
25822593 var column = complexTypeTable . FindColumn ( property ) ! ;
25832594 propertyExpressionMap [ property ] = CreateColumnExpression (
2584- property , column , tableReferenceExpression , isComplexTypeNullable || column . IsNullable ) ;
2595+ property , column , tableAlias , isComplexTypeNullable || column . IsNullable ) ;
25852596 }
25862597
25872598 // The table map of the target complex type should only ever contains a single table (no table splitting).
25882599 // If the source is itself a complex type (nested complex type), its table map is already suitable and we can just pass it on.
25892600 var newTableMap = containerProjection . TableMap . Count == 1
25902601 ? containerProjection . TableMap
2591- : new Dictionary < ITableBase , string > { [ complexTypeTable ] = tableReferenceExpression } ;
2602+ : new Dictionary < ITableBase , string > { [ complexTypeTable ] = tableAlias } ;
25922603
25932604 Check . DebugAssert ( newTableMap . Single ( ) . Key == complexTypeTable , "Bad new table map" ) ;
25942605
@@ -3530,33 +3541,35 @@ StructuralTypeProjectionExpression LiftEntityProjectionFromSubquery(
35303541 string subqueryAlias )
35313542 {
35323543 var propertyExpressions = new Dictionary < IProperty , ColumnExpression > ( ) ;
3544+ var complexPropertyCache = new Dictionary < IComplexProperty , StructuralTypeShaperExpression > ( ) ;
35333545
3534- HandleTypeProjection ( projection ) ;
3535-
3536- void HandleTypeProjection ( StructuralTypeProjectionExpression typeProjection )
3546+ foreach ( var property in projection . StructuralType . GetAllPropertiesInHierarchy ( ) )
35373547 {
3538- foreach ( var property in typeProjection . StructuralType . GetAllPropertiesInHierarchy ( ) )
3548+ // json entity projection (i.e. JSON entity that was transformed into query root) may have synthesized keys
3549+ // but they don't correspond to any columns - we need to skip those
3550+ if ( projection is { StructuralType : IEntityType entityType }
3551+ && entityType . IsMappedToJson ( )
3552+ && property . IsOrdinalKeyProperty ( ) )
35393553 {
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 ;
3554+ continue ;
35533555 }
35543556
3555- foreach ( var complexProperty in GetAllComplexPropertiesInHierarchy ( typeProjection . StructuralType ) )
3556- {
3557- HandleTypeProjection (
3558- ( StructuralTypeProjectionExpression ) typeProjection . BindComplexProperty ( complexProperty ) . ValueBufferExpression ) ;
3559- }
3557+ var innerColumn = projection . BindProperty ( property ) ;
3558+ var outerColumn = subquery . GenerateOuterColumn ( subqueryAlias , innerColumn ) ;
3559+
3560+ projectionMap [ innerColumn ] = outerColumn ;
3561+ propertyExpressions [ property ] = outerColumn ;
3562+ }
3563+
3564+ foreach ( var complexProperty in GetAllComplexPropertiesInHierarchy ( projection . StructuralType ) )
3565+ {
3566+ var complexPropertyShaper = projection . BindComplexProperty ( complexProperty ) ;
3567+
3568+ var complexTypeProjectionExpression = LiftEntityProjectionFromSubquery (
3569+ ( StructuralTypeProjectionExpression ) complexPropertyShaper . ValueBufferExpression ,
3570+ subqueryAlias ) ;
3571+
3572+ complexPropertyCache [ complexProperty ] = complexPropertyShaper . Update ( complexTypeProjectionExpression ) ;
35603573 }
35613574
35623575 ColumnExpression ? discriminatorExpression = null ;
@@ -3570,7 +3583,7 @@ void HandleTypeProjection(StructuralTypeProjectionExpression typeProjection)
35703583 var tableMap = projection . TableMap . ToDictionary ( kvp => kvp . Key , _ => subqueryAlias ) ;
35713584
35723585 var newEntityProjection = new StructuralTypeProjectionExpression (
3573- projection . StructuralType , propertyExpressions , tableMap , nullable : false , discriminatorExpression ) ;
3586+ projection . StructuralType , propertyExpressions , complexPropertyCache , tableMap , nullable : false , discriminatorExpression ) ;
35743587
35753588 if ( projection . StructuralType is IEntityType entityType2 )
35763589 {
0 commit comments