From efdcbf51af80fcdfbfa51af6c97227659b7c6b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Tue, 9 May 2023 21:43:18 +0200 Subject: [PATCH 1/7] Add test case for #3290 --- .../NHSpecificTest/GH3290/Entity.cs | 13 ++ .../NHSpecificTest/GH3290/Fixture.cs | 131 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 src/NHibernate.Test/NHSpecificTest/GH3290/Entity.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs diff --git a/src/NHibernate.Test/NHSpecificTest/GH3290/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3290/Entity.cs new file mode 100644 index 00000000000..0ef176af019 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3290/Entity.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH3290 +{ + class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual ISet Parents { get; set; } + public virtual ISet Children { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs new file mode 100644 index 00000000000..1011f97246e --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3290 +{ + [TestFixture] + public class Fixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb)); + + rc.Property( + x => x.Name + ); + + rc.Set( + x => x.Children, + v => + { + v.Table("EntityToEntity"); + v.Cascade(Mapping.ByCode.Cascade.None); + v.Inverse(true); + v.Key(x => + { + x.Column("ParentId"); + x.NotNullable(true); + }); + v.Lazy(CollectionLazy.Lazy); + v.Fetch(CollectionFetchMode.Join); + }, + h => h.ManyToMany(m => m.Column("ChildId")) + ); + + rc.Set( + x => x.Parents, + v => + { + v.Table("EntityToEntity"); + v.Cascade(Mapping.ByCode.Cascade.All); + + v.Key(x => + { + x.Column("ChildId"); + x.NotNullable(true); + }); + v.Lazy(CollectionLazy.Lazy); + v.Fetch(CollectionFetchMode.Join); + }, + h => h.ManyToMany(m => m.Column("ParentId")) + ); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void Configure(Configuration configuration) + { + // Workaround fo the test case: + /* + configuration.SetProperty(Environment.DetectFetchLoops, "false"); + configuration.SetProperty(Environment.MaxFetchDepth, "2"); + */ + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + var person = new Entity + { + Name = "pers", + Parents = new HashSet() + }; + session.Save(person); + var job = new Entity + { + Name = "job", + Children = new HashSet() + }; + session.Save(job); + + job.Children.Add(person); + person.Parents.Add(job); + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + session.CreateSQLQuery("delete EntityToEntity").ExecuteUpdate(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public void QueryWithFetch() + { + using var session = OpenSession(); + using var _ = session.BeginTransaction(); + + var all = session + .QueryOver() + .Fetch(SelectMode.Fetch, x => x.Children) + .Fetch(SelectMode.Fetch, x => x.Parents) + .TransformUsing(Transformers.DistinctRootEntity) + .List(); + + foreach (var entity in all) + { + var isPerson = entity.Name == "pers"; + if (isPerson) + Assert.That(entity.Parents, Has.Count.EqualTo(1), "Person's job not found or non-unique."); + else + Assert.That(entity.Children, Has.Count.EqualTo(1), "Job's employee not found or non-unique."); + } + } + } +} From 371fc87d58fdd87ed3013f1e80e769f5d0a97fe0 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 12 Jun 2023 11:15:43 +0300 Subject: [PATCH 2/7] Enable both cases --- .../NHSpecificTest/GH3290/Fixture.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs index 1011f97246e..932bbf4bc0b 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs @@ -7,9 +7,17 @@ namespace NHibernate.Test.NHSpecificTest.GH3290 { - [TestFixture] + [TestFixture(true)] + [TestFixture(false)] public class Fixture : TestCaseMappingByCode { + private readonly bool _detectFetchLoops; + + public Fixture(bool detectFetchLoops) + { + _detectFetchLoops = detectFetchLoops; + } + protected override HbmMapping GetMappings() { var mapper = new ModelMapper(); @@ -63,11 +71,11 @@ protected override HbmMapping GetMappings() protected override void Configure(Configuration configuration) { - // Workaround fo the test case: - /* + if (_detectFetchLoops) + return; + configuration.SetProperty(Environment.DetectFetchLoops, "false"); configuration.SetProperty(Environment.MaxFetchDepth, "2"); - */ } protected override void OnSetUp() @@ -99,7 +107,7 @@ protected override void OnTearDown() using var session = OpenSession(); using var transaction = session.BeginTransaction(); - session.CreateSQLQuery("delete EntityToEntity").ExecuteUpdate(); + session.CreateSQLQuery("delete from EntityToEntity").ExecuteUpdate(); session.CreateQuery("delete from System.Object").ExecuteUpdate(); transaction.Commit(); From b845845446963fac6608163fe8fcc08aac7cb556 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 12 Jun 2023 08:28:11 +0000 Subject: [PATCH 3/7] Generate async files --- .../Async/NHSpecificTest/GH3290/Fixture.cs | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs new file mode 100644 index 00000000000..c044a714d0f --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs @@ -0,0 +1,150 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Collections.Generic; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3290 +{ + using System.Threading.Tasks; + [TestFixture(true)] + [TestFixture(false)] + public class FixtureAsync : TestCaseMappingByCode + { + private readonly bool _detectFetchLoops; + + public FixtureAsync(bool detectFetchLoops) + { + _detectFetchLoops = detectFetchLoops; + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb)); + + rc.Property( + x => x.Name + ); + + rc.Set( + x => x.Children, + v => + { + v.Table("EntityToEntity"); + v.Cascade(Mapping.ByCode.Cascade.None); + v.Inverse(true); + v.Key(x => + { + x.Column("ParentId"); + x.NotNullable(true); + }); + v.Lazy(CollectionLazy.Lazy); + v.Fetch(CollectionFetchMode.Join); + }, + h => h.ManyToMany(m => m.Column("ChildId")) + ); + + rc.Set( + x => x.Parents, + v => + { + v.Table("EntityToEntity"); + v.Cascade(Mapping.ByCode.Cascade.All); + + v.Key(x => + { + x.Column("ChildId"); + x.NotNullable(true); + }); + v.Lazy(CollectionLazy.Lazy); + v.Fetch(CollectionFetchMode.Join); + }, + h => h.ManyToMany(m => m.Column("ParentId")) + ); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void Configure(Configuration configuration) + { + if (_detectFetchLoops) + return; + + configuration.SetProperty(Environment.DetectFetchLoops, "false"); + configuration.SetProperty(Environment.MaxFetchDepth, "2"); + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + var person = new Entity + { + Name = "pers", + Parents = new HashSet() + }; + session.Save(person); + var job = new Entity + { + Name = "job", + Children = new HashSet() + }; + session.Save(job); + + job.Children.Add(person); + person.Parents.Add(job); + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + session.CreateSQLQuery("delete from EntityToEntity").ExecuteUpdate(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public async Task QueryWithFetchAsync() + { + using var session = OpenSession(); + using var _ = session.BeginTransaction(); + + var all = await (session + .QueryOver() + .Fetch(SelectMode.Fetch, x => x.Children) + .Fetch(SelectMode.Fetch, x => x.Parents) + .TransformUsing(Transformers.DistinctRootEntity) + .ListAsync()); + + foreach (var entity in all) + { + var isPerson = entity.Name == "pers"; + if (isPerson) + Assert.That(entity.Parents, Has.Count.EqualTo(1), "Person's job not found or non-unique."); + else + Assert.That(entity.Children, Has.Count.EqualTo(1), "Job's employee not found or non-unique."); + } + } + } +} From b33095eaece8e782e6f48f91435f232917391e9d Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 12 Jun 2023 11:35:01 +0300 Subject: [PATCH 4/7] Fix --- src/NHibernate/Loader/JoinWalker.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 731ee2cc670..65d7594395c 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -192,7 +192,15 @@ private void AddAssociationToJoinTree(IAssociationType type, string[] aliasedLhs if (qc != null) { - _joinQueue.Enqueue(new CollectionJoinQueueEntry(qc, subalias, path, pathAlias)); + var collection = new CollectionJoinQueueEntry(qc, subalias, path, pathAlias); + // Many-to-Many element entity join needs to be added right after collection bridge table + // (see IsManyToManyWith, ManyToManySelectFragment, IsManyToManyRoot usages) + if (qc.IsManyToMany) + { + collection.Walk(this); + return; + } + _joinQueue.Enqueue(collection); } else if (joinable is IOuterJoinLoadable jl) { From 897f83769c1b868a8bda6a09a55ce5af2afd8e80 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 13 Jun 2023 10:22:06 +0300 Subject: [PATCH 5/7] Fix SqlServerCe for #3314 --- src/NHibernate.Test/NHSpecificTest/GH3288/Entity.cs | 4 +++- src/NHibernate.Test/NHSpecificTest/GH3288/Mappings.hbm.xml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH3288/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3288/Entity.cs index 0e917134d11..9f85c760a14 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH3288/Entity.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH3288/Entity.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace NHibernate.Test.NHSpecificTest.GH3288 { @@ -10,6 +11,7 @@ class TopEntity class MiddleEntity { public virtual int Id { get; set; } + public virtual string Name { get; set; } public virtual ISet Components { get; set; } = new HashSet(); } diff --git a/src/NHibernate.Test/NHSpecificTest/GH3288/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3288/Mappings.hbm.xml index 94ca5e12a95..b9cd68ded6f 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH3288/Mappings.hbm.xml +++ b/src/NHibernate.Test/NHSpecificTest/GH3288/Mappings.hbm.xml @@ -13,6 +13,7 @@ + From c40a391c2193fc42c3014e5a38443525859277a2 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 13 Jun 2023 10:22:46 +0300 Subject: [PATCH 6/7] Explicit fetch loop detection configuration --- src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs index 932bbf4bc0b..f7c6fe8dbe0 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs @@ -71,11 +71,8 @@ protected override HbmMapping GetMappings() protected override void Configure(Configuration configuration) { - if (_detectFetchLoops) - return; - - configuration.SetProperty(Environment.DetectFetchLoops, "false"); - configuration.SetProperty(Environment.MaxFetchDepth, "2"); + configuration.SetProperty(Environment.DetectFetchLoops, _detectFetchLoops ? "true" : "false"); + configuration.SetProperty(Environment.MaxFetchDepth, _detectFetchLoops ? "-1" : "2"); } protected override void OnSetUp() From 433ff19b5a2c1a3b6746a9a7a19f1e215cbcc158 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 13 Jun 2023 07:26:23 +0000 Subject: [PATCH 7/7] Generate async files --- src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs index c044a714d0f..77229193cbf 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs @@ -82,11 +82,8 @@ protected override HbmMapping GetMappings() protected override void Configure(Configuration configuration) { - if (_detectFetchLoops) - return; - - configuration.SetProperty(Environment.DetectFetchLoops, "false"); - configuration.SetProperty(Environment.MaxFetchDepth, "2"); + configuration.SetProperty(Environment.DetectFetchLoops, _detectFetchLoops ? "true" : "false"); + configuration.SetProperty(Environment.MaxFetchDepth, _detectFetchLoops ? "-1" : "2"); } protected override void OnSetUp()