diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 98739ab10c3..e2337cc1203 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -1387,18 +1387,34 @@ public Expression ApplyCollectionJoin( var identifierFromParent = _identifier; if (innerSelectExpression.Tables.LastOrDefault(e => e is InnerJoinExpression) is InnerJoinExpression - collectionInnerJoinExpression - && collectionInnerJoinExpression.Table is SelectExpression collectionInnerSelectExpression) - { - // This computes true parent identifier count for correlation. - // The last inner joined table in innerSelectExpression brings collection data. - // Further tables load additional data on the collection (hence uses outer pattern) - // So identifier not coming from there (which would be at the start only) are for correlation with parent. - // Parent can have additional identifier if a owned reference was expanded. - var actualParentIdentifierCount = innerSelectExpression._identifier - .TakeWhile(e => !ReferenceEquals(e.Column.Table, collectionInnerSelectExpression)) - .Count(); - identifierFromParent = _identifier.Take(actualParentIdentifierCount).ToList(); + collectionInnerJoinExpression) + { + TableExpressionBase tableToMatch = null; + var useOldBehavior = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue23111", out var isEnabled) + && isEnabled; + if (useOldBehavior + && collectionInnerJoinExpression.Table is SelectExpression collectionInnerSelectExpression) + { + tableToMatch = collectionInnerSelectExpression; + } + else if (!useOldBehavior + && collectionInnerJoinExpression.Table is TableExpressionBase collectionTableExpressionBase) + { + tableToMatch = collectionTableExpressionBase; + } + + if (tableToMatch != null) + { + // This computes true parent identifier count for correlation. + // The last inner joined table in innerSelectExpression brings collection data. + // Further tables load additional data on the collection (hence uses outer pattern) + // So identifier not coming from there (which would be at the start only) are for correlation with parent. + // Parent can have additional identifier if a owned reference was expanded. + var actualParentIdentifierCount = innerSelectExpression._identifier + .TakeWhile(e => !ReferenceEquals(e.Column.Table, tableToMatch)) + .Count(); + identifierFromParent = _identifier.Take(actualParentIdentifierCount).ToList(); + } } var parentIdentifier = GetIdentifierAccessor(identifierFromParent).Item1; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 5dcee3c6f7d..9ff61110109 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -9139,6 +9139,157 @@ private SqlServerTestStore CreateDatabase22568() #endregion + #region Issue23211 + + [ConditionalFact] + public virtual void Collection_include_on_owner_with_two_owned_types_mapped_to_different_table() + { + using (CreateDatabase23211()) + { + using var context = new MyContext23211(_options); + + var owner = context.Set().Include(e => e.Dependents).AsSplitQuery().OrderBy(e => e.Id).Single(); + Assert.NotNull(owner.Dependents); + Assert.Equal(2, owner.Dependents.Count); + Assert.NotNull(owner.Owned1); + Assert.Equal("A", owner.Owned1.Value); + Assert.NotNull(owner.Owned2); + Assert.Equal("B", owner.Owned2.Value); + + AssertSql( + @"SELECT [t].[Id], [t].[Owner23211Id], [t].[Value], [t].[Owner23211Id0], [t].[Value0] +FROM ( + SELECT TOP(2) [o].[Id], [o0].[Owner23211Id], [o0].[Value], [o1].[Owner23211Id] AS [Owner23211Id0], [o1].[Value] AS [Value0] + FROM [Owner23211] AS [o] + LEFT JOIN [Owned123211] AS [o0] ON [o].[Id] = [o0].[Owner23211Id] + LEFT JOIN [Owned223211] AS [o1] ON [o].[Id] = [o1].[Owner23211Id] + ORDER BY [o].[Id] +) AS [t] +ORDER BY [t].[Id]", + // + @"SELECT [d].[Id], [d].[Owner23211Id], [t].[Id] +FROM ( + SELECT TOP(1) [o].[Id] + FROM [Owner23211] AS [o] + ORDER BY [o].[Id] +) AS [t] +INNER JOIN [Dependent23211] AS [d] ON [t].[Id] = [d].[Owner23211Id] +ORDER BY [t].[Id]"); + } + } + + [ConditionalFact] + public virtual void Collection_include_on_owner_with_owned_type_mapped_to_different_table() + { + using (CreateDatabase23211()) + { + using var context = new MyContext23211(_options); + + var owner = context.Set().Include(e => e.Dependents).AsSplitQuery().OrderBy(e => e.Id).Single(); + Assert.NotNull(owner.Dependents); + Assert.Equal(2, owner.Dependents.Count); + Assert.NotNull(owner.Owned); + Assert.Equal("A", owner.Owned.Value); + + AssertSql( + @"SELECT [t].[Id], [t].[SecondOwner23211Id], [t].[Value] +FROM ( + SELECT TOP(2) [s].[Id], [o].[SecondOwner23211Id], [o].[Value] + FROM [SecondOwner23211] AS [s] + LEFT JOIN [Owned23211] AS [o] ON [s].[Id] = [o].[SecondOwner23211Id] + ORDER BY [s].[Id] +) AS [t] +ORDER BY [t].[Id]", + // + @"SELECT [s0].[Id], [s0].[SecondOwner23211Id], [t].[Id] +FROM ( + SELECT TOP(1) [s].[Id] + FROM [SecondOwner23211] AS [s] + ORDER BY [s].[Id] +) AS [t] +INNER JOIN [SecondDependent23211] AS [s0] ON [t].[Id] = [s0].[SecondOwner23211Id] +ORDER BY [t].[Id]"); + } + } + + private class Owner23211 + { + public int Id { get; set; } + public List Dependents { get; set; } + public OwnedType23211 Owned1 { get; set; } + public OwnedType23211 Owned2 { get; set; } + } + + private class OwnedType23211 + { + public string Value { get; set; } + } + + private class Dependent23211 + { + public int Id { get; set; } + } + + private class SecondOwner23211 + { + public int Id { get; set; } + public List Dependents { get; set; } + public OwnedType23211 Owned { get; set; } + } + + private class SecondDependent23211 + { + public int Id { get; set; } + } + + private class MyContext23211 : DbContext + { + public MyContext23211(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().OwnsOne(e => e.Owned1, b => b.ToTable("Owned123211")); + modelBuilder.Entity().OwnsOne(e => e.Owned2, b => b.ToTable("Owned223211")); + modelBuilder.Entity().OwnsOne(e => e.Owned, b => b.ToTable("Owned23211")); + } + } + + private SqlServerTestStore CreateDatabase23211() + => CreateTestStore( + () => new MyContext23211(_options), + context => + { + context.Add(new Owner23211 + { + Dependents = new List + { + new Dependent23211(), + new Dependent23211() + }, + Owned1 = new OwnedType23211 { Value = "A" }, + Owned2 = new OwnedType23211 { Value = "B" } + }); + + context.Add(new SecondOwner23211 + { + Dependents = new List + { + new SecondDependent23211(), + new SecondDependent23211() + }, + Owned = new OwnedType23211 { Value = "A" } + }); + + context.SaveChanges(); + + ClearLog(); + }); + + #endregion + private DbContextOptions _options; private SqlServerTestStore CreateTestStore(