diff --git a/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs index a512b608b12..44cb0a3993b 100644 --- a/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs @@ -1516,6 +1516,19 @@ var innerShapedQuery return false; } + if (!(AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue9892", out var enabled) && enabled)) + { + if (!IsFlattenableGroupJoinDefaultIfEmpty(groupJoinClause, queryModel, index)) + { + var shaperType = innerShapedQuery?.Arguments.Last().Type; + if (shaperType == null + || !typeof(EntityShaper).IsAssignableFrom(shaperType)) + { + return false; + } + } + } + var joinClause = groupJoinClause.JoinClause; var outerQuerySource = FindPreviousQuerySource(queryModel, index); @@ -1630,6 +1643,34 @@ var newShapedQueryMethod return true; } + private bool IsFlattenableGroupJoinDefaultIfEmpty( + [NotNull] GroupJoinClause groupJoinClause, + QueryModel queryModel, + int index) + { + var additionalFromClause + = queryModel.BodyClauses.ElementAtOrDefault(index + 1) + as AdditionalFromClause; + + var subQueryModel + = (additionalFromClause?.FromExpression as SubQueryExpression) + ?.QueryModel; + + var referencedQuerySource + = subQueryModel?.MainFromClause.FromExpression.TryGetReferencedQuerySource(); + + if (referencedQuerySource != groupJoinClause + || queryModel.CountQuerySourceReferences(groupJoinClause) != 1 + || subQueryModel.BodyClauses.Count != 0 + || subQueryModel.ResultOperators.Count != 1 + || !(subQueryModel.ResultOperators[0] is DefaultIfEmptyResultOperator)) + { + return false; + } + + return true; + } + private bool TryFlattenGroupJoinDefaultIfEmpty( [NotNull] GroupJoinClause groupJoinClause, QueryModel queryModel, diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 8c655c3424a..ed891a1e716 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -2689,6 +2689,98 @@ public class EntityWithLocalVariableAccessInFilter9825 #endregion + #region Bug9892 + + [Fact] + public virtual void GroupJoin_to_parent_with_no_child_works_9892() + { + using (CreateDatabase9892()) + { + using (var context = new MyContext9892(_options)) + { + var results = ( + from p in context.Parents + join c in ( + from x in context.Children + select new + { + ParentId = x.ParentId, + OtherParent = x.OtherParent.Name + }) + on p.Id equals c.ParentId into child + select new + { + ParentId = p.Id, + ParentName = p.Name, + Children = child.Select(c => c.OtherParent) + }).ToList(); + + Assert.Equal(3, results.Count); + Assert.Single(results.Where(t => !t.Children.Any())); + } + } + } + + private SqlServerTestStore CreateDatabase9892() + => CreateTestStore( + () => new MyContext9892(_options), + context => + { + context.Parents.Add(new Parent9892 { Name = "Parent1" }); + context.Parents.Add(new Parent9892 { Name = "Parent2" }); + context.Parents.Add(new Parent9892 { Name = "Parent3" }); + + context.OtherParents.Add(new OtherParent9892 { Name = "OtherParent1" }); + context.OtherParents.Add(new OtherParent9892 { Name = "OtherParent2" }); + + context.SaveChanges(); + + context.Children.Add(new Child9892 { ParentId = 1, OtherParentId = 1 }); + context.Children.Add(new Child9892 { ParentId = 1, OtherParentId = 2 }); + context.Children.Add(new Child9892 { ParentId = 2, OtherParentId = 1 }); + context.Children.Add(new Child9892 { ParentId = 2, OtherParentId = 2 }); + + context.SaveChanges(); + + ClearLog(); + }); + + public class MyContext9892 : DbContext + { + public MyContext9892(DbContextOptions options) + : base(options) + { + } + + public DbSet Parents { get; set; } + public DbSet Children { get; set; } + public DbSet OtherParents { get; set; } + } + + public class Parent9892 + { + public int Id { get; set; } + public string Name { get; set; } + public List Children { get; set; } + } + + public class OtherParent9892 + { + public int Id { get; set; } + public string Name { get; set; } + } + + public class Child9892 + { + public int Id { get; set; } + public int ParentId { get; set; } + public Parent9892 Parent { get; set; } + public int OtherParentId { get; set; } + public OtherParent9892 OtherParent { get; set; } + } + + #endregion + private DbContextOptions _options; private SqlServerTestStore CreateTestStore(