@@ -25,8 +25,11 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;
25
25
public sealed partial class SelectExpression : TableExpressionBase
26
26
{
27
27
private const string DiscriminatorColumnAlias = "Discriminator" ;
28
+
28
29
private static readonly bool UseOldBehavior31107 =
29
30
AppContext . TryGetSwitch ( "Microsoft.EntityFrameworkCore.Issue31107" , out var enabled31107 ) && enabled31107 ;
31
+ private static readonly bool UseOldBehavior32234 =
32
+ AppContext . TryGetSwitch ( "Microsoft.EntityFrameworkCore.Issue32234" , out var enabled32234 ) && enabled32234 ;
30
33
31
34
private static readonly IdentifierComparer IdentifierComparerInstance = new ( ) ;
32
35
@@ -4612,6 +4615,9 @@ private static string GenerateUniqueAlias(HashSet<string> usedAliases, string cu
4612
4615
4613
4616
/// <inheritdoc />
4614
4617
protected override Expression VisitChildren ( ExpressionVisitor visitor )
4618
+ => VisitChildren ( visitor , updateColumns : true ) ;
4619
+
4620
+ private Expression VisitChildren ( ExpressionVisitor visitor , bool updateColumns )
4615
4621
{
4616
4622
if ( _mutable )
4617
4623
{
@@ -4800,14 +4806,38 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
4800
4806
newSelectExpression . _childIdentifiers . AddRange (
4801
4807
childIdentifier . Zip ( _childIdentifiers ) . Select ( e => ( e . First , e . Second . Comparer ) ) ) ;
4802
4808
4803
- // Remap tableReferences in new select expression
4804
- foreach ( var tableReference in newTableReferences )
4809
+ // We duplicated the SelectExpression, and must therefore also update all table reference expressions to point to it.
4810
+ // If any tables have changed, we must duplicate the TableReferenceExpressions and replace all ColumnExpressions to use
4811
+ // them; otherwise we end up two SelectExpressions sharing the same TableReferenceExpression instance, and if that's later
4812
+ // mutated, both SelectExpressions are affected (this happened in AliasUniquifier, see #32234).
4813
+
4814
+ // Otherwise, if no tables have changed, we mutate the TableReferenceExpressions (this was the previous code, left it for
4815
+ // a more low-risk fix). Note that updateColumns is false only if we're already being called from
4816
+ // ColumnTableReferenceUpdater to replace the ColumnExpressions, in which case we avoid infinite recursion.
4817
+ if ( tablesChanged && updateColumns && ! UseOldBehavior32234 )
4805
4818
{
4806
- tableReference . UpdateTableReference ( this , newSelectExpression ) ;
4819
+ for ( var i = 0 ; i < newTableReferences . Count ; i ++ )
4820
+ {
4821
+ newTableReferences [ i ] = new TableReferenceExpression ( newSelectExpression , _tableReferences [ i ] . Alias ) ;
4822
+ }
4823
+
4824
+ var columnTableReferenceUpdater = new ColumnTableReferenceUpdater ( this , newSelectExpression ) ;
4825
+ newSelectExpression = ( SelectExpression ) columnTableReferenceUpdater . Visit ( newSelectExpression ) ;
4807
4826
}
4827
+ else
4828
+ {
4829
+ // Remap tableReferences in new select expression
4830
+ foreach ( var tableReference in newTableReferences )
4831
+ {
4832
+ tableReference . UpdateTableReference ( this , newSelectExpression ) ;
4833
+ }
4808
4834
4809
- var tableReferenceUpdatingExpressionVisitor = new TableReferenceUpdatingExpressionVisitor ( this , newSelectExpression ) ;
4810
- tableReferenceUpdatingExpressionVisitor . Visit ( newSelectExpression ) ;
4835
+ // TODO: Why does need to be done? We've already updated all table references on the new select just above, and
4836
+ // no ColumnExpression in the query is every supposed to reference a TableReferenceExpression that isn't in the
4837
+ // select's list... The same thing is done in all other places where TableReferenceUpdatingExpressionVisitor is used.
4838
+ var tableReferenceUpdatingExpressionVisitor = new TableReferenceUpdatingExpressionVisitor ( this , newSelectExpression ) ;
4839
+ tableReferenceUpdatingExpressionVisitor . Visit ( newSelectExpression ) ;
4840
+ }
4811
4841
4812
4842
return newSelectExpression ;
4813
4843
}
0 commit comments