From be2d9f8d9a7a63e1bb9410a2caa1d919fb31c08a Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Fri, 9 Apr 2021 15:41:30 -0400 Subject: [PATCH 01/28] GH2201: Refactor JoinWalker and related subclasses to traverse the entity graph level by level. --- .../Loader/AbstractEntityJoinWalker.cs | 1 + .../Loader/Criteria/CriteriaJoinWalker.cs | 12 +- src/NHibernate/Loader/JoinWalker.cs | 109 ++++++++++++++++-- 3 files changed, 104 insertions(+), 18 deletions(-) diff --git a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs index 5fa438cc85a..4eb40d53210 100644 --- a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs +++ b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs @@ -31,6 +31,7 @@ public AbstractEntityJoinWalker(string rootSqlAlias, IOuterJoinLoadable persiste protected virtual void InitAll(SqlString whereString, SqlString orderByString, LockMode lockMode) { AddAssociations(); + ProcessJoins(); IList allAssociations = new List(associations); var rootAssociation = CreateRootAssociation(); allAssociations.Add(rootAssociation); diff --git a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs index 107163b7b68..40452dc65a6 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs @@ -86,15 +86,15 @@ protected override void AddAssociations() AddExplicitEntityJoinAssociation(persister, tableAlias, translator.GetJoinType(criteriaPath, criteriaPathAlias), criteriaPath, criteriaPathAlias); IncludeInResultIfNeeded(persister, entityJoinInfo.Criteria, tableAlias, criteriaPath); //collect mapped associations for entity join - WalkEntityTree(persister, tableAlias, criteriaPath, 1); + WalkEntityTree(persister, tableAlias, criteriaPath); } } - protected override void WalkEntityTree(IOuterJoinLoadable persister, string alias, string path, int currentDepth) + protected override void WalkEntityTree(IOuterJoinLoadable persister, string alias, string path) { // NH different behavior (NH-1476, NH-1760, NH-1785) - base.WalkEntityTree(persister, alias, path, currentDepth); - WalkCompositeComponentIdTree(persister, alias, path, currentDepth); + base.WalkEntityTree(persister, alias, path); + WalkCompositeComponentIdTree(persister, alias, path); } protected override OuterJoinableAssociation CreateRootAssociation() @@ -130,14 +130,14 @@ protected override ISet GetEntityFetchLazyProperties(string path) return translator.RootCriteria.GetEntityFetchLazyProperties(path); } - private void WalkCompositeComponentIdTree(IOuterJoinLoadable persister, string alias, string path, int currentDepth) + private void WalkCompositeComponentIdTree(IOuterJoinLoadable persister, string alias, string path) { IType type = persister.IdentifierType; string propertyName = persister.IdentifierPropertyName; if (type != null && type.IsComponentType) { ILhsAssociationTypeSqlInfo associationTypeSQLInfo = JoinHelper.GetIdLhsSqlInfo(alias, persister, Factory); - WalkComponentTree((IAbstractComponentType) type, 0, alias, SubPath(path, propertyName), currentDepth, associationTypeSQLInfo); + WalkComponentTree((IAbstractComponentType) type, 0, alias, SubPath(path, propertyName), associationTypeSQLInfo); } } diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 85094060477..28519e5345c 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -15,6 +15,8 @@ namespace NHibernate.Loader public class JoinWalker { private readonly ISessionFactoryImplementor factory; + private Queue joinQueue = new Queue(); + private int depth; protected readonly IList associations = new List(); private readonly HashSet visitedAssociationKeys = new HashSet(); private readonly IDictionary enabledFilters; @@ -188,17 +190,28 @@ private void AddAssociationToJoinTree(IAssociationType type, string[] aliasedLhs assoc.ValidateJoin(path); AddAssociation(assoc); - int nextDepth = currentDepth + 1; - - if (qc == null) + if (qc != null) { - IOuterJoinLoadable pjl = joinable as IOuterJoinLoadable; - if (pjl != null) - WalkEntityTree(pjl, subalias, path, nextDepth); + joinQueue.Enqueue( + new CollectionQueueEntry() + { + Persister = qc, + Alias = subalias, + Path = path, + PathAlias = pathAlias, + } + ); } - else + else if (joinable is IOuterJoinLoadable jl) { - WalkCollectionTree(qc, subalias, path, pathAlias, nextDepth); + joinQueue.Enqueue( + new EntityQueueEntry() + { + Persister = jl, + Alias = subalias, + Path = path, + } + ); } } @@ -299,7 +312,8 @@ private void AddAssociation(OuterJoinableAssociation association) /// protected void WalkEntityTree(IOuterJoinLoadable persister, string alias) { - WalkEntityTree(persister, alias, string.Empty, 0); + WalkEntityTree(persister, alias, String.Empty); + ProcessJoins(); } /// @@ -307,8 +321,29 @@ protected void WalkEntityTree(IOuterJoinLoadable persister, string alias) /// protected void WalkCollectionTree(IQueryableCollection persister, string alias) { - WalkCollectionTree(persister, alias, string.Empty, string.Empty, 0); - //TODO: when this is the entry point, we should use an INNER_JOIN for fetching the many-to-many elements! + WalkCollectionTree(persister, alias, String.Empty, String.Empty, depth); + ProcessJoins(); + } + + protected void ProcessJoins() + { + while(joinQueue.Count > 0) + { + QueueEntry entry = joinQueue.Dequeue(); + + switch(entry) + { + case CollectionQueueEntry ce: + WalkCollectionTree(ce.Persister, ce.Alias, ce.Path, ce.PathAlias, depth); + break; + case EntityQueueEntry eqe: + WalkEntityTree(eqe.Persister, eqe.Alias, eqe.Path); + break; + default: + depth++; + break; + } + } } /// @@ -318,7 +353,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st { if (persister.IsOneToMany) { - WalkEntityTree((IOuterJoinLoadable)persister.ElementPersister, alias, path, currentDepth); + WalkEntityTree((IOuterJoinLoadable)persister.ElementPersister, alias, path); } else { @@ -439,13 +474,30 @@ private void WalkEntityAssociationTree(IAssociationType associationType, IOuterJ /// For an entity class, add to a list of associations to be fetched /// by outerjoin /// + /// Since 5.4 + protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias, string path) + { + // TODO: inline function +#pragma warning disable 618 + WalkEntityTree(persister, alias, path, depth); +#pragma warning restore 618 + } + + /// + /// For an entity class, add to a list of associations to be fetched + /// by outerjoin + /// + [Obsolete("Use or override the overload without the currentDepth parameter")] protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias, string path, int currentDepth) { int n = persister.CountSubclassProperties(); + + joinQueue.Enqueue(null); for (int i = 0; i < n; i++) { IType type = persister.GetSubclassPropertyType(i); ILhsAssociationTypeSqlInfo associationTypeSQLInfo = JoinHelper.GetLhsSqlInfo(alias, i, persister, Factory); + if (type.IsAssociationType) { WalkEntityAssociationTree((IAssociationType) type, persister, i, alias, path, @@ -462,6 +514,20 @@ protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias /// /// For a component, add to a list of associations to be fetched by outerjoin /// + /// Since 5.4 + protected void WalkComponentTree(IAbstractComponentType componentType, int begin, string alias, string path, + ILhsAssociationTypeSqlInfo associationTypeSQLInfo) + { + // TODO: inline function +#pragma warning disable 618 + WalkComponentTree(componentType, begin, alias, path, depth, associationTypeSQLInfo); +#pragma warning restore 618 + } + + /// + /// For a component, add to a list of associations to be fetched by outerjoin + /// + [Obsolete("Use or override the overload without the currentDepth parameter")] protected void WalkComponentTree(IAbstractComponentType componentType, int begin, string alias, string path, int currentDepth, ILhsAssociationTypeSqlInfo associationTypeSQLInfo) { @@ -1201,5 +1267,24 @@ protected static string GetSelectFragment(OuterJoinableAssociation join, string { return join.GetSelectFragment(entitySuffix, collectionSuffix, next); } + + protected abstract class QueueEntry { } + + protected abstract class QueueEntry : QueueEntry where T : IJoinable + { + public string Alias { get; set; } + public T Persister { get; set; } + } + + protected class EntityQueueEntry : QueueEntry where T : IJoinable + { + public string Path { get; set; } + } + protected class EntityQueueEntry : EntityQueueEntry {} + + protected class CollectionQueueEntry : EntityQueueEntry + { + public string PathAlias { get; set; } + } } } From d33d453ae45acb2806f57b84cb8ffe9c11682904 Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Wed, 7 Apr 2021 10:55:07 -0400 Subject: [PATCH 02/28] GH2201: Correct failing tests due to change in how objects are loaded. --- .../AbstractEntityWithOneToManyTest.cs | 3 +-- .../AbstractEntityWithOneToManyTest.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/NHibernate.Test/Async/Immutable/EntityWithMutableCollection/AbstractEntityWithOneToManyTest.cs b/src/NHibernate.Test/Async/Immutable/EntityWithMutableCollection/AbstractEntityWithOneToManyTest.cs index 5ba2f200fbb..7aa992c5c4d 100644 --- a/src/NHibernate.Test/Async/Immutable/EntityWithMutableCollection/AbstractEntityWithOneToManyTest.cs +++ b/src/NHibernate.Test/Async/Immutable/EntityWithMutableCollection/AbstractEntityWithOneToManyTest.cs @@ -1234,7 +1234,6 @@ public virtual async Task OneToManyCollectionOptimisticLockingWithUpdateAsync() s = OpenSession(); t = s.BeginTransaction(); - c = await (s.CreateCriteria().UniqueResultAsync()); // If the entity uses a join mapping, DML queries require temp tables. if (Dialect.SupportsTemporaryTables) await (s.CreateQuery("delete from Party").ExecuteUpdateAsync()); @@ -1251,7 +1250,7 @@ public virtual async Task OneToManyCollectionOptimisticLockingWithUpdateAsync() await (s.DeleteAsync(partyOrig)); await (s.DeleteAsync(newParty)); } - + c = await (s.CreateCriteria().UniqueResultAsync()); await (s.DeleteAsync(c)); Assert.That(await (s.CreateCriteria().SetProjection(Projections.RowCountInt64()).UniqueResultAsync()), Is.EqualTo(0L)); Assert.That(await (s.CreateCriteria().SetProjection(Projections.RowCountInt64()).UniqueResultAsync()), Is.EqualTo(0L)); diff --git a/src/NHibernate.Test/Immutable/EntityWithMutableCollection/AbstractEntityWithOneToManyTest.cs b/src/NHibernate.Test/Immutable/EntityWithMutableCollection/AbstractEntityWithOneToManyTest.cs index 012e55c5a3c..71ed408923c 100644 --- a/src/NHibernate.Test/Immutable/EntityWithMutableCollection/AbstractEntityWithOneToManyTest.cs +++ b/src/NHibernate.Test/Immutable/EntityWithMutableCollection/AbstractEntityWithOneToManyTest.cs @@ -1223,7 +1223,6 @@ public virtual void OneToManyCollectionOptimisticLockingWithUpdate() s = OpenSession(); t = s.BeginTransaction(); - c = s.CreateCriteria().UniqueResult(); // If the entity uses a join mapping, DML queries require temp tables. if (Dialect.SupportsTemporaryTables) s.CreateQuery("delete from Party").ExecuteUpdate(); @@ -1240,7 +1239,7 @@ public virtual void OneToManyCollectionOptimisticLockingWithUpdate() s.Delete(partyOrig); s.Delete(newParty); } - + c = s.CreateCriteria().UniqueResult(); s.Delete(c); Assert.That(s.CreateCriteria().SetProjection(Projections.RowCountInt64()).UniqueResult(), Is.EqualTo(0L)); Assert.That(s.CreateCriteria().SetProjection(Projections.RowCountInt64()).UniqueResult(), Is.EqualTo(0L)); From a37e27877c260efe230780ea6ebccaa8cc28fd06 Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Mon, 5 Apr 2021 16:08:59 -0400 Subject: [PATCH 03/28] GH2201: Add tests for checking if fetch loops is enabled/disabled. --- .../NHSpecificTest/GH2201/Detail.cs | 9 ++ .../NHSpecificTest/GH2201/Fixture.cs | 122 ++++++++++++++++++ .../NHSpecificTest/GH2201/Mappings.hbm.xml | 23 ++++ .../NHSpecificTest/GH2201/Order.cs | 11 ++ .../NHSpecificTest/GH2201/Person.cs | 32 +++++ 5 files changed, 197 insertions(+) create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2201/Detail.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2201/Mappings.hbm.xml create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2201/Order.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2201/Person.cs diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/Detail.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/Detail.cs new file mode 100644 index 00000000000..9b4c3bf1c33 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/Detail.cs @@ -0,0 +1,9 @@ +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + public class Detail + { + public virtual int Id { get; protected set; } + public virtual Person Person { get; set; } + public virtual string Data { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs new file mode 100644 index 00000000000..8405e4b712a --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Stat; +using NUnit.Framework; +using NHCfg = NHibernate.Cfg; + +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void Configure(NHCfg.Configuration configuration) + { + configuration.SetProperty(NHCfg.Environment.GenerateStatistics, "true"); + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.Delete("from Person"); + + tx.Commit(); + } + } + + protected override void OnSetUp() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + string[] names = { "Alice", "Bob" }; + + for (int i = 0; i < names.Length; i++) + { + var name = names[i]; + + Person parent = new Person() + { + Name = name, + Details = new Detail() + { + Data = $"Details for ${name}" + } + }; + + for (int j = 1; j <= 3; j++) + { + Person child = new Person() + { + Name = $"Child ${j} of ${parent.Name}", + Parent = parent, + Details = new Detail() + { + Data = $"Details for child ${j} of ${name}" + } + }; + + parent.Children.Add(child); + } + + s.Save(parent); + } + + tx.Commit(); + } + } + + [Test] + public void QueryOverPersonWithParent() + { + var stats = Sfi.Statistics; + + stats.Clear(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var people = s.QueryOver() + .Fetch(SelectMode.Fetch,p => p.Parent) + .Where(p => p.Parent != null) + .List(); + + foreach (Person p in people) + { + Assert.That(p.Parent, Is.Not.Null); + Assert.That(p.Parent.Details, Is.Not.Null); + } + + Assert.That(people.Count, Is.EqualTo(6)); + Assert.That(stats.QueryExecutionCount, Is.EqualTo(1)); + Assert.That(stats.EntityFetchCount, Is.EqualTo(0)); + Assert.That(stats.EntityLoadCount, Is.EqualTo(16)); + } + } + + [Test] + public void QueryOverSinglePersonWithParent() + { + var stats = Sfi.Statistics; + + stats.Clear(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var person = s.QueryOver() + .Where(p => p.Parent != null) + .Fetch(SelectMode.Fetch, p => p.Parent) + .Take(1) + .SingleOrDefault(); + + Assert.That(person, Is.Not.Null); + Assert.That(stats.QueryExecutionCount, Is.EqualTo(1)); + Assert.That(stats.EntityFetchCount, Is.EqualTo(0)); + Assert.That(stats.EntityLoadCount, Is.EqualTo(4)); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH2201/Mappings.hbm.xml new file mode 100644 index 00000000000..04cabf557ba --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/Mappings.hbm.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + Person + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/Order.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/Order.cs new file mode 100644 index 00000000000..00c4023ac2a --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/Order.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + public class Order + { + public virtual int Id { get; set; } + public virtual Person Person { get; set; } + public virtual DateTime Date { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/Person.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/Person.cs new file mode 100644 index 00000000000..619fe916647 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/Person.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + public class Person + { + private Detail _detail; + + public Person() + { + this.Children = new HashSet(); + } + + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual Person Parent { get; set; } + public virtual ISet Children { get; protected set; } + public virtual Detail Details + { + get { return _detail; } + set + { + _detail = value; + + if (_detail != null) + { + _detail.Person = this; + } + } + } + } +} From cda10f6963f933cf9aa5c3a6934a230a9ea9e29a Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Mon, 5 Apr 2021 15:50:38 -0400 Subject: [PATCH 04/28] GH2201: Add option to disable loop detection during query fetches. --- src/NHibernate/Cfg/Environment.cs | 6 ++++++ src/NHibernate/Cfg/Settings.cs | 1 + src/NHibernate/Cfg/SettingsFactory.cs | 4 ++++ src/NHibernate/Loader/JoinWalker.cs | 11 +++++++++-- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs index f8da86cecae..50bbba63b23 100644 --- a/src/NHibernate/Cfg/Environment.cs +++ b/src/NHibernate/Cfg/Environment.cs @@ -110,6 +110,12 @@ public static string Version public const string CurrentSessionContextClass = "current_session_context_class"; public const string UseSqlComments = "use_sql_comments"; + /// + /// Enable or disable the ability to detect loops in query fetches. + /// The default is to detect and elimate potential fetch loops. + /// + public const string DetectFetchLoops = "detect_fetch_loops"; + /// Enable formatting of SQL logged to the console public const string FormatSql = "format_sql"; diff --git a/src/NHibernate/Cfg/Settings.cs b/src/NHibernate/Cfg/Settings.cs index 7009598b188..99c556757b9 100644 --- a/src/NHibernate/Cfg/Settings.cs +++ b/src/NHibernate/Cfg/Settings.cs @@ -36,6 +36,7 @@ public Settings() public SqlStatementLogger SqlStatementLogger { get; internal set; } public int MaximumFetchDepth { get; internal set; } + public bool DetectFetchLoops { get; internal set; } public IDictionary QuerySubstitutions { get; internal set; } diff --git a/src/NHibernate/Cfg/SettingsFactory.cs b/src/NHibernate/Cfg/SettingsFactory.cs index 4a606a08f83..d1df28df3ac 100644 --- a/src/NHibernate/Cfg/SettingsFactory.cs +++ b/src/NHibernate/Cfg/SettingsFactory.cs @@ -92,6 +92,10 @@ public Settings BuildSettings(IDictionary properties) log.Info("Maximum outer join fetch depth: {0}", maxFetchDepth); } + bool detectFetchLoops = PropertiesHelper.GetBoolean(Environment.DetectFetchLoops, properties, true); + log.Info("Detect fetch loops: {0}", EnabledDisabled(detectFetchLoops)); + settings.DetectFetchLoops = detectFetchLoops; + IConnectionProvider connectionProvider = ConnectionProviderFactory.NewConnectionProvider(properties); ITransactionFactory transactionFactory = CreateTransactionFactory(properties); // TransactionManagerLookup transactionManagerLookup = TransactionManagerLookupFactory.GetTransactionManagerLookup( properties ); diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 28519e5345c..6c1960ca97c 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -797,8 +797,15 @@ protected virtual string GenerateRootAlias(string description) /// protected virtual bool IsDuplicateAssociation(string foreignKeyTable, string[] foreignKeyColumns) { - AssociationKey associationKey = new AssociationKey(foreignKeyColumns, foreignKeyTable); - return !visitedAssociationKeys.Add(associationKey); + bool detectFetchLoops = Factory.Settings.DetectFetchLoops; + + if (detectFetchLoops) + { + AssociationKey associationKey = new AssociationKey(foreignKeyColumns, foreignKeyTable); + return !visitedAssociationKeys.Add(associationKey); + } + + return false; } /// From 15d580aed927caafff2076dec1a32c5cdd478c56 Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Mon, 5 Apr 2021 21:40:02 -0400 Subject: [PATCH 05/28] GH2201: Disable loop detection in query fetches to fix tests. --- src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs index 8405e4b712a..a5a120b5c98 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs @@ -13,6 +13,7 @@ public class Fixture : BugTestCase protected override void Configure(NHCfg.Configuration configuration) { configuration.SetProperty(NHCfg.Environment.GenerateStatistics, "true"); + configuration.SetProperty(NHCfg.Environment.DetectFetchLoops, "false"); } protected override void OnTearDown() From 625836140bc8ec368fec66f06faf32261289beb0 Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Tue, 6 Apr 2021 09:00:27 -0400 Subject: [PATCH 06/28] GH2201: Add async versions of fetch loop detection tests. --- .../Async/NHSpecificTest/GH2201/Fixture.cs | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH2201/Fixture.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/Fixture.cs new file mode 100644 index 00000000000..62dbb9d3680 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/Fixture.cs @@ -0,0 +1,134 @@ +//------------------------------------------------------------------------------ +// +// 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; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Stat; +using NUnit.Framework; +using NHCfg = NHibernate.Cfg; + +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override void Configure(NHCfg.Configuration configuration) + { + configuration.SetProperty(NHCfg.Environment.GenerateStatistics, "true"); + configuration.SetProperty(NHCfg.Environment.DetectFetchLoops, "false"); + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.Delete("from Person"); + + tx.Commit(); + } + } + + protected override void OnSetUp() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + string[] names = { "Alice", "Bob" }; + + for (int i = 0; i < names.Length; i++) + { + var name = names[i]; + + Person parent = new Person() + { + Name = name, + Details = new Detail() + { + Data = $"Details for ${name}" + } + }; + + for (int j = 1; j <= 3; j++) + { + Person child = new Person() + { + Name = $"Child ${j} of ${parent.Name}", + Parent = parent, + Details = new Detail() + { + Data = $"Details for child ${j} of ${name}" + } + }; + + parent.Children.Add(child); + } + + s.Save(parent); + } + + tx.Commit(); + } + } + + [Test] + public async Task QueryOverPersonWithParentAsync() + { + var stats = Sfi.Statistics; + + stats.Clear(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var people = await (s.QueryOver() + .Fetch(SelectMode.Fetch,p => p.Parent) + .Where(p => p.Parent != null) + .ListAsync()); + + foreach (Person p in people) + { + Assert.That(p.Parent, Is.Not.Null); + Assert.That(p.Parent.Details, Is.Not.Null); + } + + Assert.That(people.Count, Is.EqualTo(6)); + Assert.That(stats.QueryExecutionCount, Is.EqualTo(1)); + Assert.That(stats.EntityFetchCount, Is.EqualTo(0)); + Assert.That(stats.EntityLoadCount, Is.EqualTo(16)); + } + } + + [Test] + public async Task QueryOverSinglePersonWithParentAsync() + { + var stats = Sfi.Statistics; + + stats.Clear(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var person = await (s.QueryOver() + .Where(p => p.Parent != null) + .Fetch(SelectMode.Fetch, p => p.Parent) + .Take(1) + .SingleOrDefaultAsync()); + + Assert.That(person, Is.Not.Null); + Assert.That(stats.QueryExecutionCount, Is.EqualTo(1)); + Assert.That(stats.EntityFetchCount, Is.EqualTo(0)); + Assert.That(stats.EntityLoadCount, Is.EqualTo(4)); + } + } + } +} From 0a4790af9f0b0e91b5820360c0a12e710748794b Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Tue, 6 Apr 2021 09:17:46 -0400 Subject: [PATCH 07/28] GH2201: Add 'detect_fetch_loops' option to nhibernate-configuration XML schema definition file. --- src/NHibernate/nhibernate-configuration.xsd | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NHibernate/nhibernate-configuration.xsd b/src/NHibernate/nhibernate-configuration.xsd index fd068653cba..979765b0fb2 100644 --- a/src/NHibernate/nhibernate-configuration.xsd +++ b/src/NHibernate/nhibernate-configuration.xsd @@ -91,6 +91,7 @@ + From 4a124575b5c1f47fb17da85190a3fc9988c606d4 Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Tue, 13 Apr 2021 09:24:23 -0400 Subject: [PATCH 08/28] GH2201: Remove unused currentDepth parameter from private WalkCollectionTree function. --- src/NHibernate/Loader/JoinWalker.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 6c1960ca97c..fb140e54b78 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -321,7 +321,7 @@ protected void WalkEntityTree(IOuterJoinLoadable persister, string alias) /// protected void WalkCollectionTree(IQueryableCollection persister, string alias) { - WalkCollectionTree(persister, alias, String.Empty, String.Empty, depth); + WalkCollectionTree(persister, alias, String.Empty, String.Empty); ProcessJoins(); } @@ -334,7 +334,7 @@ protected void ProcessJoins() switch(entry) { case CollectionQueueEntry ce: - WalkCollectionTree(ce.Persister, ce.Alias, ce.Path, ce.PathAlias, depth); + WalkCollectionTree(ce.Persister, ce.Alias, ce.Path, ce.PathAlias); break; case EntityQueueEntry eqe: WalkEntityTree(eqe.Persister, eqe.Alias, eqe.Path); @@ -349,7 +349,7 @@ protected void ProcessJoins() /// /// For a collection role, return a list of associations to be fetched by outerjoin /// - private void WalkCollectionTree(IQueryableCollection persister, string alias, string path, string pathAlias, int currentDepth) + private void WalkCollectionTree(IQueryableCollection persister, string alias, string path, string pathAlias) { if (persister.IsOneToMany) { @@ -370,7 +370,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st // if the current depth is 0, the root thing being loaded is the // many-to-many collection itself. Here, it is alright to use // an inner join... - bool useInnerJoin = currentDepth == 0; + bool useInnerJoin = depth == 0; var joinType = GetJoinType( @@ -381,7 +381,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st persister.TableName, lhsColumns, !useInnerJoin, - currentDepth - 1, + depth - 1, null); AddAssociationToJoinTreeIfNecessary( @@ -390,7 +390,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st alias, path, pathAlias, - currentDepth - 1, + depth - 1, joinType); } else if (type.IsComponentType) @@ -401,7 +401,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st persister, alias, path, - currentDepth); + depth); } } } From be705743376bf838c4fb0a0e83c932aaeb4e0952 Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Tue, 13 Apr 2021 14:53:55 -0400 Subject: [PATCH 09/28] GH2201: Remove unused currentDepth parameter from AddAssociationToJoinTree. --- src/NHibernate/Loader/JoinWalker.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index fb140e54b78..2166efec29a 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -138,11 +138,11 @@ protected JoinWalker(ISessionFactoryImplementor factory, IDictionary private void AddAssociationToJoinTreeIfNecessary(IAssociationType type, string[] aliasedLhsColumns, - string alias, string path, string pathAlias, int currentDepth, JoinType joinType) + string alias, string path, string pathAlias, JoinType joinType) { if (joinType >= JoinType.InnerJoin) { - AddAssociationToJoinTree(type, aliasedLhsColumns, alias, path, pathAlias, currentDepth, joinType); + AddAssociationToJoinTree(type, aliasedLhsColumns, alias, path, pathAlias, joinType); } } @@ -166,7 +166,7 @@ protected virtual SqlString GetWithClause(string path, string pathAlias) /// of associations to be fetched by outerjoin /// private void AddAssociationToJoinTree(IAssociationType type, string[] aliasedLhsColumns, string alias, - string path, string pathAlias, int currentDepth, JoinType joinType) + string path, string pathAlias, JoinType joinType) { IJoinable joinable = type.GetAssociatedJoinable(Factory); @@ -390,7 +390,6 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st alias, path, pathAlias, - depth - 1, joinType); } else if (type.IsComponentType) @@ -465,7 +464,6 @@ private void WalkEntityAssociationTree(IAssociationType associationType, IOuterJ alias, subpath, subPathAlias, - currentDepth, joinType); } } @@ -567,7 +565,6 @@ protected void WalkComponentTree(IAbstractComponentType componentType, int begin alias, subpath, subPathAlias, - currentDepth, joinType); } } @@ -626,7 +623,6 @@ private void WalkCompositeElementTree(IAbstractComponentType compositeType, stri alias, subpath, subPathAlias, - currentDepth, joinType); } } From d86bbd9cb52bafeee5691bff888d05978344c4bb Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Tue, 13 Apr 2021 14:59:52 -0400 Subject: [PATCH 10/28] GH2201: Replace currentDepth parameter in WalkCompositeElementTree with depth instance variable. --- src/NHibernate/Loader/JoinWalker.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 2166efec29a..842b8407267 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -399,8 +399,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st persister.ElementColumnNames, persister, alias, - path, - depth); + path); } } } @@ -582,7 +581,7 @@ protected void WalkComponentTree(IAbstractComponentType componentType, int begin /// For a composite element, add to a list of associations to be fetched by outerjoin /// private void WalkCompositeElementTree(IAbstractComponentType compositeType, string[] cols, - IQueryableCollection persister, string alias, string path, int currentDepth) + IQueryableCollection persister, string alias, string path) { IType[] types = compositeType.Subtypes; string[] propertyNames = compositeType.PropertyNames; @@ -614,7 +613,7 @@ private void WalkCompositeElementTree(IAbstractComponentType compositeType, stri persister.TableName, lhsColumns, propertyNullability == null || propertyNullability[i], - currentDepth, + depth, compositeType.GetCascadeStyle(i)); AddAssociationToJoinTreeIfNecessary( @@ -634,8 +633,7 @@ private void WalkCompositeElementTree(IAbstractComponentType compositeType, stri lhsColumns, persister, alias, - subpath, - currentDepth); + subpath); } begin += length; } From 023b022c7362a499cbfa323cde28fb71e0accf93 Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Tue, 13 Apr 2021 15:14:03 -0400 Subject: [PATCH 11/28] GH2201: Remove uses of the now obsolete version of WalkComponentTree. --- src/NHibernate/Loader/JoinWalker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 842b8407267..5b098966e5d 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -503,7 +503,7 @@ protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias else if (type.IsComponentType) { WalkComponentTree((IAbstractComponentType) type, 0, alias, SubPath(path, persister.GetSubclassPropertyName(i)), - currentDepth, associationTypeSQLInfo); + associationTypeSQLInfo); } } } @@ -571,7 +571,7 @@ protected void WalkComponentTree(IAbstractComponentType componentType, int begin { string subpath = SubPath(path, propertyNames[i]); - WalkComponentTree((IAbstractComponentType) types[i], begin, alias, subpath, currentDepth, associationTypeSQLInfo); + WalkComponentTree((IAbstractComponentType) types[i], begin, alias, subpath, associationTypeSQLInfo); } begin += types[i].GetColumnSpan(Factory); } From 975b977f0f6ff80a0ba7fecff0111237bec5b274 Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Tue, 13 Apr 2021 15:29:37 -0400 Subject: [PATCH 12/28] GH2201: Remove currentDepth parameter from private WalkEntityAssociationTree method. --- src/NHibernate/Loader/JoinWalker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 5b098966e5d..e3392f7a04e 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -433,7 +433,7 @@ internal OuterJoinableAssociation InitAssociation(OuterJoinableAssociation assoc } private void WalkEntityAssociationTree(IAssociationType associationType, IOuterJoinLoadable persister, - int propertyNumber, string alias, string path, bool nullable, int currentDepth, + int propertyNumber, string alias, string path, bool nullable, ILhsAssociationTypeSqlInfo associationTypeSQLInfo) { string[] aliasedLhsColumns = associationTypeSQLInfo.GetAliasedColumnNames(associationType, 0); @@ -454,7 +454,7 @@ private void WalkEntityAssociationTree(IAssociationType associationType, IOuterJ lhsTable, lhsColumns, nullable, - currentDepth, + depth, persister.GetCascadeStyle(propertyNumber)); AddAssociationToJoinTreeIfNecessary( @@ -498,7 +498,7 @@ protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias if (type.IsAssociationType) { WalkEntityAssociationTree((IAssociationType) type, persister, i, alias, path, - persister.IsSubclassPropertyNullable(i), currentDepth, associationTypeSQLInfo); + persister.IsSubclassPropertyNullable(i), associationTypeSQLInfo); } else if (type.IsComponentType) { From e99caefcd72e003b48ce28524da3d486fcb4384c Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Tue, 13 Apr 2021 15:30:42 -0400 Subject: [PATCH 13/28] GH2201: Replace uses of currentDepth parameter in obsolete version of WalkEntityTree with the private depth member variable. --- src/NHibernate/Loader/JoinWalker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index e3392f7a04e..83f85208a21 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -555,7 +555,7 @@ protected void WalkComponentTree(IAbstractComponentType componentType, int begin lhsTable, lhsColumns, propertyNullability == null || propertyNullability[i], - currentDepth, + depth, componentType.GetCascadeStyle(i)); AddAssociationToJoinTreeIfNecessary( From dc9f5007cc6f758604c23a5a430e781f48cf477d Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sat, 20 Aug 2022 23:06:22 +0300 Subject: [PATCH 14/28] More tests; Failing test for entity join --- .../CircularReferenceFetchDepth0Fixture.cs | 61 +++++++++ .../CircularReferenceFetchDepth1Fixture.cs | 61 +++++++++ .../GH2201/CircularReferenceFetchFixture.cs | 44 +++++++ ...ure.cs => DetectFetchLoopsFalseFixture.cs} | 8 +- .../NHSpecificTest/GH2201/BaseFetchFixture.cs | 119 ++++++++++++++++++ .../CircularReferenceFetchDepth0Fixture.cs | 50 ++++++++ .../CircularReferenceFetchDepth1Fixture.cs | 50 ++++++++ .../GH2201/CircularReferenceFetchFixture.cs | 33 +++++ ...ure.cs => DetectFetchLoopsFalseFixture.cs} | 10 +- .../NHSpecificTest/GH2201/Entity.cs | 11 ++ 10 files changed, 434 insertions(+), 13 deletions(-) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs rename src/NHibernate.Test/Async/NHSpecificTest/GH2201/{Fixture.cs => DetectFetchLoopsFalseFixture.cs} (94%) create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs rename src/NHibernate.Test/NHSpecificTest/GH2201/{Fixture.cs => DetectFetchLoopsFalseFixture.cs} (92%) create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2201/Entity.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs new file mode 100644 index 00000000000..ef45a124928 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs @@ -0,0 +1,61 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NHibernate.Cfg; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + using System.Threading.Tasks; + [TestFixture] + public class CircularReferenceFetchDepth0FixtureAsync : BaseFetchFixture + { + private int _id2; + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty("max_fetch_depth", "0"); + base.Configure(configuration); + } + protected override void OnSetUp() + { + base.OnSetUp(); + _id2 = _id; + //Generate another test entity + base.OnSetUp(); + } + + [Test] + public async Task QueryOverAsync() + { + using (var session = OpenSession()) + { + Entity e1 = null; + Entity e2 = null; + var result = await (session.QueryOver(() => e1) + .JoinEntityAlias(() => e2, () => e2.EntityNumber == e1.EntityNumber && e2.EntityId != _id) + .Where(e => e.EntityId == _id).SingleOrDefaultAsync()); + + VerifyChildrenNotInitialized(result); + VerifyChildrenNotInitialized(await (session.LoadAsync(_id2))); + } + } + + [Test] + public async Task GetAsync() + { + using (var session = OpenSession()) + { + var result = await (session.GetAsync(_id)); + VerifyChildrenNotInitialized(result); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs new file mode 100644 index 00000000000..f5986a11b3d --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs @@ -0,0 +1,61 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NHibernate.Cfg; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + using System.Threading.Tasks; + [TestFixture] + public class CircularReferenceFetchDepth1FixtureAsync : BaseFetchFixture + { + private int _id2; + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty("max_fetch_depth", "1"); + base.Configure(configuration); + } + protected override void OnSetUp() + { + base.OnSetUp(); + _id2 = _id; + //Generate another test entity + base.OnSetUp(); + } + + [Test] + public async Task QueryOverAsync() + { + using (var session = OpenSession()) + { + Entity e1 = null; + Entity e2 = null; + var result = await (session.QueryOver(() => e1) + .JoinEntityAlias(() => e2, () => e2.EntityNumber == e1.EntityNumber && e2.EntityId != _id) + .Where(e => e.EntityId == _id).SingleOrDefaultAsync()); + + Verify(result); + Verify(await (session.LoadAsync(_id2))); + } + } + + [Test] + public async Task GetAsync() + { + using (var session = OpenSession()) + { + var result = await (session.GetAsync(_id)); + Verify(result); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs new file mode 100644 index 00000000000..84f21d58ef8 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs @@ -0,0 +1,44 @@ +//------------------------------------------------------------------------------ +// +// 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; +using System.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + using System.Threading.Tasks; + + [TestFixture] + public class CircularReferenceFetchFixtureAsync : BaseFetchFixture + { + [Test] + public async Task QueryOverAsync() + { + using (var session = OpenSession()) + { + var result = await (session.QueryOver().Where(e => e.EntityNumber == "Bob").SingleOrDefaultAsync()); + + Verify(result); + } + } + + [Test] + public async Task GetAsync() + { + using (var session = OpenSession()) + { + var result = await (session.GetAsync(_id)); + + Verify(result); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs similarity index 94% rename from src/NHibernate.Test/Async/NHSpecificTest/GH2201/Fixture.cs rename to src/NHibernate.Test/Async/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs index 62dbb9d3680..d226bb67ce8 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs @@ -8,10 +8,6 @@ //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; -using NHibernate.Stat; using NUnit.Framework; using NHCfg = NHibernate.Cfg; @@ -19,7 +15,7 @@ namespace NHibernate.Test.NHSpecificTest.GH2201 { using System.Threading.Tasks; [TestFixture] - public class FixtureAsync : BugTestCase + public class DetectFetchLoopsFalseFixtureAsync : BugTestCase { protected override void Configure(NHCfg.Configuration configuration) { @@ -91,7 +87,7 @@ public async Task QueryOverPersonWithParentAsync() using (var tx = s.BeginTransaction()) { var people = await (s.QueryOver() - .Fetch(SelectMode.Fetch,p => p.Parent) + .Fetch(SelectMode.Fetch, p => p.Parent) .Where(p => p.Parent != null) .ListAsync()); diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs new file mode 100644 index 00000000000..6c64c2c4a21 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs @@ -0,0 +1,119 @@ +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + public class BaseFetchFixture : TestCaseMappingByCode + { + protected int _id; + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(m => + { + m.Id(c => c.EntityId, id => + { + id.Generator(Generators.Native); + }); + m.Property(c => c.EntityNumber); + m.ManyToOne(c => c.ReferencedEntity, p => + { + p.Column("ReferencedEntityId"); + p.Fetch(FetchKind.Join); + p.Cascade(Mapping.ByCode.Cascade.Persist); + p.ForeignKey("none"); + }); + m.ManyToOne(c => c.AdditionalEntity, p => + { + p.Column("AdditionalEntityId"); + p.Fetch(FetchKind.Join); + p.Cascade(Mapping.ByCode.Cascade.Persist); + p.ForeignKey("none"); + }); + m.ManyToOne(c => c.SourceEntity, p => + { + p.Column("SourceEntityId"); + p.Fetch(FetchKind.Join); + p.Cascade(Mapping.ByCode.Cascade.Persist); + p.ForeignKey("none"); + }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity + { + EntityNumber = "Bob", + AdditionalEntity = CreateEntity(), + ReferencedEntity = CreateEntity(), + SourceEntity = CreateEntity(), + }; + session.Save(e1); + transaction.Commit(); + _id = e1.EntityId; + } + } + private static Entity CreateEntity() + { + return new Entity + { + SourceEntity = new Entity(), + AdditionalEntity = new Entity(), + ReferencedEntity = new Entity(), + }; + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + protected static void Verify(Entity result) + { + + VerifyChildrenInitialized(result); + + VerifyChildrenNotInitialized(result.AdditionalEntity); + VerifyChildrenNotInitialized(result.SourceEntity); + VerifyChildrenNotInitialized(result.ReferencedEntity); + } + + protected static void VerifyChildrenInitialized(Entity result) + { + Assert.That(result, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result), Is.True); + Assert.That(result.AdditionalEntity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.AdditionalEntity), Is.True); + Assert.That(result.SourceEntity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.SourceEntity), Is.True); + Assert.That(result.ReferencedEntity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.ReferencedEntity), Is.True); + } + + protected static void VerifyChildrenNotInitialized(Entity result) + { + Assert.That(result, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result), Is.True); + Assert.That(result.AdditionalEntity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.AdditionalEntity), Is.False); + Assert.That(result.SourceEntity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.SourceEntity), Is.False); + Assert.That(result.ReferencedEntity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.ReferencedEntity), Is.False); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs new file mode 100644 index 00000000000..72540e95551 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs @@ -0,0 +1,50 @@ +using NHibernate.Cfg; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + [TestFixture] + public class CircularReferenceFetchDepth0Fixture : BaseFetchFixture + { + private int _id2; + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty("max_fetch_depth", "0"); + base.Configure(configuration); + } + protected override void OnSetUp() + { + base.OnSetUp(); + _id2 = _id; + //Generate another test entity + base.OnSetUp(); + } + + [Test] + public void QueryOver() + { + using (var session = OpenSession()) + { + Entity e1 = null; + Entity e2 = null; + var result = session.QueryOver(() => e1) + .JoinEntityAlias(() => e2, () => e2.EntityNumber == e1.EntityNumber && e2.EntityId != _id) + .Where(e => e.EntityId == _id).SingleOrDefault(); + + VerifyChildrenNotInitialized(result); + VerifyChildrenNotInitialized(session.Load(_id2)); + } + } + + [Test] + public void Get() + { + using (var session = OpenSession()) + { + var result = session.Get(_id); + VerifyChildrenNotInitialized(result); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs new file mode 100644 index 00000000000..8797eb51364 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs @@ -0,0 +1,50 @@ +using NHibernate.Cfg; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + [TestFixture] + public class CircularReferenceFetchDepth1Fixture : BaseFetchFixture + { + private int _id2; + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty("max_fetch_depth", "1"); + base.Configure(configuration); + } + protected override void OnSetUp() + { + base.OnSetUp(); + _id2 = _id; + //Generate another test entity + base.OnSetUp(); + } + + [Test] + public void QueryOver() + { + using (var session = OpenSession()) + { + Entity e1 = null; + Entity e2 = null; + var result = session.QueryOver(() => e1) + .JoinEntityAlias(() => e2, () => e2.EntityNumber == e1.EntityNumber && e2.EntityId != _id) + .Where(e => e.EntityId == _id).SingleOrDefault(); + + Verify(result); + Verify(session.Load(_id2)); + } + } + + [Test] + public void Get() + { + using (var session = OpenSession()) + { + var result = session.Get(_id); + Verify(result); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs new file mode 100644 index 00000000000..37098e19454 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + + [TestFixture] + public class CircularReferenceFetchFixture : BaseFetchFixture + { + [Test] + public void QueryOver() + { + using (var session = OpenSession()) + { + var result = session.QueryOver().Where(e => e.EntityNumber == "Bob").SingleOrDefault(); + + Verify(result); + } + } + + [Test] + public void Get() + { + using (var session = OpenSession()) + { + var result = session.Get(_id); + + Verify(result); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs similarity index 92% rename from src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs rename to src/NHibernate.Test/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs index a5a120b5c98..dcf841b8604 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs @@ -1,14 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NHibernate.Stat; -using NUnit.Framework; +using NUnit.Framework; using NHCfg = NHibernate.Cfg; namespace NHibernate.Test.NHSpecificTest.GH2201 { [TestFixture] - public class Fixture : BugTestCase + public class DetectFetchLoopsFalseFixture : BugTestCase { protected override void Configure(NHCfg.Configuration configuration) { @@ -80,7 +76,7 @@ public void QueryOverPersonWithParent() using (var tx = s.BeginTransaction()) { var people = s.QueryOver() - .Fetch(SelectMode.Fetch,p => p.Parent) + .Fetch(SelectMode.Fetch, p => p.Parent) .Where(p => p.Parent != null) .List(); diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/Entity.cs new file mode 100644 index 00000000000..f9492014e15 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/Entity.cs @@ -0,0 +1,11 @@ +namespace NHibernate.Test.NHSpecificTest.GH2201 +{ + public class Entity + { + public virtual int EntityId { get; set; } + public virtual string EntityNumber { get; set; } + public virtual Entity ReferencedEntity { get; set; } + public virtual Entity AdditionalEntity { get; set; } + public virtual Entity SourceEntity { get; set; } + } +} From fc4aa6889444b4b47992be22dab4ac2389335284 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 21 Aug 2022 15:40:44 +0300 Subject: [PATCH 15/28] Fix entity join fetching --- src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs | 1 + src/NHibernate/Loader/JoinWalker.cs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs index 40452dc65a6..ee609e733b4 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs @@ -87,6 +87,7 @@ protected override void AddAssociations() IncludeInResultIfNeeded(persister, entityJoinInfo.Criteria, tableAlias, criteriaPath); //collect mapped associations for entity join WalkEntityTree(persister, tableAlias, criteriaPath); + ProcessJoins(); } } diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index d2428b44f03..c39a79aae90 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -411,6 +411,8 @@ internal void AddExplicitEntityJoinAssociation( string path, string pathAlias) { + depth = 0; + visitedAssociationKeys.Clear(); OuterJoinableAssociation assoc = InitAssociation(new OuterJoinableAssociation( persister.EntityType, @@ -665,7 +667,7 @@ protected virtual JoinType GetJoinType(IAssociationType type, FetchMode config, if (IsTooDeep(currentDepth) || (type.IsCollectionType && IsTooManyCollections)) return JoinType.None; - bool dupe = IsDuplicateAssociation(lhsTable, lhsColumns, type); + bool dupe = IsDuplicateAssociation(lhsTable, lhsColumns, type) && currentDepth > 0;//NOTE: currentDepth check must be executed after dup check (to properly collect dup info) if (dupe) return JoinType.None; From a7cd4332a71bf84335863d20d1e8ddc1630ad996 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 21 Aug 2022 15:55:06 +0300 Subject: [PATCH 16/28] Undo not needed change --- src/NHibernate/Loader/JoinWalker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index c39a79aae90..b5e921a1a94 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -667,7 +667,7 @@ protected virtual JoinType GetJoinType(IAssociationType type, FetchMode config, if (IsTooDeep(currentDepth) || (type.IsCollectionType && IsTooManyCollections)) return JoinType.None; - bool dupe = IsDuplicateAssociation(lhsTable, lhsColumns, type) && currentDepth > 0;//NOTE: currentDepth check must be executed after dup check (to properly collect dup info) + bool dupe = IsDuplicateAssociation(lhsTable, lhsColumns, type); if (dupe) return JoinType.None; From 21b39ab49d7d056bdee4f3e25f07393a61411793 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 21 Aug 2022 15:59:32 +0300 Subject: [PATCH 17/28] More tests --- .../CircularReferenceFetchDepth0Fixture.cs | 5 ++ ... => CircularReferenceFetchDepthFixture.cs} | 25 ++++++-- .../GH2201/CircularReferenceFetchFixture.cs | 4 ++ .../NHSpecificTest/GH2201/BaseFetchFixture.cs | 64 +++++++++++++++++-- .../CircularReferenceFetchDepth0Fixture.cs | 5 ++ ... => CircularReferenceFetchDepthFixture.cs} | 25 ++++++-- .../GH2201/CircularReferenceFetchFixture.cs | 4 ++ .../NHSpecificTest/GH2201/Entity.cs | 14 ++++ 8 files changed, 129 insertions(+), 17 deletions(-) rename src/NHibernate.Test/Async/NHSpecificTest/GH2201/{CircularReferenceFetchDepth1Fixture.cs => CircularReferenceFetchDepthFixture.cs} (68%) rename src/NHibernate.Test/NHSpecificTest/GH2201/{CircularReferenceFetchDepth1Fixture.cs => CircularReferenceFetchDepthFixture.cs} (59%) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs index ef45a124928..89586bfd9fb 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs @@ -19,11 +19,16 @@ public class CircularReferenceFetchDepth0FixtureAsync : BaseFetchFixture { private int _id2; + public CircularReferenceFetchDepth0FixtureAsync() : base(0) + { + } + protected override void Configure(Configuration configuration) { configuration.SetProperty("max_fetch_depth", "0"); base.Configure(configuration); } + protected override void OnSetUp() { base.OnSetUp(); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs similarity index 68% rename from src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs rename to src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs index f5986a11b3d..da9c96f8c85 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs @@ -14,37 +14,52 @@ namespace NHibernate.Test.NHSpecificTest.GH2201 { using System.Threading.Tasks; - [TestFixture] - public class CircularReferenceFetchDepth1FixtureAsync : BaseFetchFixture + [TestFixture(1)] + [TestFixture(2)] + public class CircularReferenceFetchDepthFixtureAsync : BaseFetchFixture { private int _id2; + private int _id3; + + public CircularReferenceFetchDepthFixtureAsync(int depth) : base(depth) + { + } protected override void Configure(Configuration configuration) { - configuration.SetProperty("max_fetch_depth", "1"); + configuration.SetProperty("max_fetch_depth", _depth.ToString()); base.Configure(configuration); } + protected override void OnSetUp() { base.OnSetUp(); _id2 = _id; - //Generate another test entity + + //Generate another test entities + base.OnSetUp(); + _id3 = _id; base.OnSetUp(); } [Test] public async Task QueryOverAsync() { + using(var logSpy = new SqlLogSpy()) using (var session = OpenSession()) { Entity e1 = null; Entity e2 = null; + Entity e3 = null; var result = await (session.QueryOver(() => e1) - .JoinEntityAlias(() => e2, () => e2.EntityNumber == e1.EntityNumber && e2.EntityId != _id) + .JoinEntityAlias(() => e2, () => e2.EntityNumber == e1.EntityNumber && e2.EntityId == _id2) + .JoinEntityAlias(() => e3, () => e3.EntityNumber == e1.EntityNumber && e3.EntityId == _id3) .Where(e => e.EntityId == _id).SingleOrDefaultAsync()); Verify(result); + Verify(await (session.LoadAsync(_id2))); + Verify(await (session.LoadAsync(_id3))); } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs index 84f21d58ef8..7d117485969 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs @@ -19,6 +19,10 @@ namespace NHibernate.Test.NHSpecificTest.GH2201 [TestFixture] public class CircularReferenceFetchFixtureAsync : BaseFetchFixture { + public CircularReferenceFetchFixtureAsync() : base(-1) + { + } + [Test] public async Task QueryOverAsync() { diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs index 6c64c2c4a21..d0cd85da332 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs @@ -7,6 +7,12 @@ namespace NHibernate.Test.NHSpecificTest.GH2201 public class BaseFetchFixture : TestCaseMappingByCode { protected int _id; + protected int _depth; + + protected BaseFetchFixture(int depth) + { + _depth = depth < 0 ? int.MaxValue : depth; + } protected override HbmMapping GetMappings() { @@ -39,6 +45,38 @@ protected override HbmMapping GetMappings() p.Cascade(Mapping.ByCode.Cascade.Persist); p.ForeignKey("none"); }); + m.ManyToOne(c => c.Level1, p => + { + p.Column("Level1Id"); + p.Fetch(FetchKind.Join); + p.Cascade(Mapping.ByCode.Cascade.Persist); + p.ForeignKey("none"); + }); + }); + + mapper.Class(m => + { + m.Id(c => c.Id, id => + { + id.Generator(Generators.Native); + }); + m.Property(c => c.Name); + m.ManyToOne(c => c.Level2, p => + { + p.Column("Level2Id"); + p.Fetch(FetchKind.Join); + p.Cascade(Mapping.ByCode.Cascade.Persist); + p.ForeignKey("none"); + }); + }); + + mapper.Class(m => + { + m.Id(c => c.Id, id => + { + id.Generator(Generators.Native); + }); + m.Property(c => c.Name); }); return mapper.CompileMappingForAllExplicitlyAddedEntities(); @@ -55,6 +93,7 @@ protected override void OnSetUp() AdditionalEntity = CreateEntity(), ReferencedEntity = CreateEntity(), SourceEntity = CreateEntity(), + Level1 = new Level1Entity() { Level2 = new Level2Entity() } }; session.Save(e1); transaction.Commit(); @@ -68,6 +107,7 @@ private static Entity CreateEntity() SourceEntity = new Entity(), AdditionalEntity = new Entity(), ReferencedEntity = new Entity(), + Level1 = new Level1Entity() { Level2 = new Level2Entity() } }; } @@ -82,26 +122,34 @@ protected override void OnTearDown() } } - protected static void Verify(Entity result) + protected void Verify(Entity result) { - VerifyChildrenInitialized(result); VerifyChildrenNotInitialized(result.AdditionalEntity); VerifyChildrenNotInitialized(result.SourceEntity); VerifyChildrenNotInitialized(result.ReferencedEntity); + } - protected static void VerifyChildrenInitialized(Entity result) + protected void VerifyChildrenInitialized(Entity result) { + var isInited = _depth > 0 ? (NUnit.Framework.Constraints.IResolveConstraint) Is.True : Is.False; Assert.That(result, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(result), Is.True); + Assert.That(NHibernateUtil.IsInitialized(result), isInited); Assert.That(result.AdditionalEntity, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(result.AdditionalEntity), Is.True); + Assert.That(NHibernateUtil.IsInitialized(result.AdditionalEntity), isInited); Assert.That(result.SourceEntity, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(result.SourceEntity), Is.True); + Assert.That(NHibernateUtil.IsInitialized(result.SourceEntity), isInited); Assert.That(result.ReferencedEntity, Is.Not.Null); - Assert.That(NHibernateUtil.IsInitialized(result.ReferencedEntity), Is.True); + Assert.That(NHibernateUtil.IsInitialized(result.ReferencedEntity), isInited); + Assert.That(result.Level1, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.Level1), isInited); + if (_depth >= 1) + { + Assert.That(result.Level1.Level2, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.Level1.Level2), _depth > 1 ? Is.True : Is.False); + } } protected static void VerifyChildrenNotInitialized(Entity result) @@ -114,6 +162,8 @@ protected static void VerifyChildrenNotInitialized(Entity result) Assert.That(NHibernateUtil.IsInitialized(result.SourceEntity), Is.False); Assert.That(result.ReferencedEntity, Is.Not.Null); Assert.That(NHibernateUtil.IsInitialized(result.ReferencedEntity), Is.False); + Assert.That(result.Level1, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.Level1), Is.False); } } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs index 72540e95551..d32bf89a58a 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth0Fixture.cs @@ -8,11 +8,16 @@ public class CircularReferenceFetchDepth0Fixture : BaseFetchFixture { private int _id2; + public CircularReferenceFetchDepth0Fixture() : base(0) + { + } + protected override void Configure(Configuration configuration) { configuration.SetProperty("max_fetch_depth", "0"); base.Configure(configuration); } + protected override void OnSetUp() { base.OnSetUp(); diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs similarity index 59% rename from src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs rename to src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs index 8797eb51364..5c2dc526253 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepth1Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs @@ -3,37 +3,52 @@ namespace NHibernate.Test.NHSpecificTest.GH2201 { - [TestFixture] - public class CircularReferenceFetchDepth1Fixture : BaseFetchFixture + [TestFixture(1)] + [TestFixture(2)] + public class CircularReferenceFetchDepthFixture : BaseFetchFixture { private int _id2; + private int _id3; + + public CircularReferenceFetchDepthFixture(int depth) : base(depth) + { + } protected override void Configure(Configuration configuration) { - configuration.SetProperty("max_fetch_depth", "1"); + configuration.SetProperty("max_fetch_depth", _depth.ToString()); base.Configure(configuration); } + protected override void OnSetUp() { base.OnSetUp(); _id2 = _id; - //Generate another test entity + + //Generate another test entities + base.OnSetUp(); + _id3 = _id; base.OnSetUp(); } [Test] public void QueryOver() { + using(var logSpy = new SqlLogSpy()) using (var session = OpenSession()) { Entity e1 = null; Entity e2 = null; + Entity e3 = null; var result = session.QueryOver(() => e1) - .JoinEntityAlias(() => e2, () => e2.EntityNumber == e1.EntityNumber && e2.EntityId != _id) + .JoinEntityAlias(() => e2, () => e2.EntityNumber == e1.EntityNumber && e2.EntityId == _id2) + .JoinEntityAlias(() => e3, () => e3.EntityNumber == e1.EntityNumber && e3.EntityId == _id3) .Where(e => e.EntityId == _id).SingleOrDefault(); Verify(result); + Verify(session.Load(_id2)); + Verify(session.Load(_id3)); } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs index 37098e19454..f960eac8e5f 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs @@ -8,6 +8,10 @@ namespace NHibernate.Test.NHSpecificTest.GH2201 [TestFixture] public class CircularReferenceFetchFixture : BaseFetchFixture { + public CircularReferenceFetchFixture() : base(-1) + { + } + [Test] public void QueryOver() { diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/Entity.cs index f9492014e15..fe086dbbe2b 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/Entity.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/Entity.cs @@ -7,5 +7,19 @@ public class Entity public virtual Entity ReferencedEntity { get; set; } public virtual Entity AdditionalEntity { get; set; } public virtual Entity SourceEntity { get; set; } + public virtual Level1Entity Level1 { get; set; } + } + + public class Level1Entity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual Level2Entity Level2 { get; set; } + } + + public class Level2Entity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } } } From cb8eff3030c44b0c9e8f3f1412f7fdf9fe7d80fc Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 21 Aug 2022 16:11:38 +0300 Subject: [PATCH 18/28] Avoid QueueEntry type casting --- src/NHibernate/Loader/JoinWalker.cs | 51 +++++++++++++++++++---------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index b5e921a1a94..bde76627466 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -330,19 +330,7 @@ protected void ProcessJoins() while(joinQueue.Count > 0) { QueueEntry entry = joinQueue.Dequeue(); - - switch(entry) - { - case CollectionQueueEntry ce: - WalkCollectionTree(ce.Persister, ce.Alias, ce.Path, ce.PathAlias); - break; - case EntityQueueEntry eqe: - WalkEntityTree(eqe.Persister, eqe.Alias, eqe.Path); - break; - default: - depth++; - break; - } + entry.Walk(this, ref depth); } } @@ -491,7 +479,7 @@ protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias { int n = persister.CountSubclassProperties(); - joinQueue.Enqueue(null); + joinQueue.Enqueue(NextLevelQueueEntry.Instance); for (int i = 0; i < n; i++) { IType type = persister.GetSubclassPropertyType(i); @@ -1272,7 +1260,10 @@ protected static string GetSelectFragment(OuterJoinableAssociation join, string return join.GetSelectFragment(entitySuffix, collectionSuffix, next); } - protected abstract class QueueEntry { } + protected abstract class QueueEntry + { + public abstract void Walk(JoinWalker walker, ref int depth); + } protected abstract class QueueEntry : QueueEntry where T : IJoinable { @@ -1280,15 +1271,41 @@ protected abstract class QueueEntry : QueueEntry where T : IJoinable public T Persister { get; set; } } - protected class EntityQueueEntry : QueueEntry where T : IJoinable + protected abstract class EntityQueueEntry : QueueEntry where T : IJoinable { public string Path { get; set; } } - protected class EntityQueueEntry : EntityQueueEntry {} + + protected class EntityQueueEntry : EntityQueueEntry + { + public override void Walk(JoinWalker walker, ref int depth) + { + walker.WalkEntityTree(Persister, Alias, Path); + } + } protected class CollectionQueueEntry : EntityQueueEntry { public string PathAlias { get; set; } + + public override void Walk(JoinWalker walker, ref int depth) + { + walker.WalkCollectionTree(Persister, Alias, Path, PathAlias); + } + } + + protected class NextLevelQueueEntry : QueueEntry + { + public static NextLevelQueueEntry Instance = new NextLevelQueueEntry(); + + private NextLevelQueueEntry() + { + } + + public override void Walk(JoinWalker walker, ref int depth) + { + depth++; + } } } } From d2785cd19b78291814d5ecb900a467eac916ff0e Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 21 Aug 2022 16:19:39 +0300 Subject: [PATCH 19/28] missing readonly modifier --- src/NHibernate/Loader/JoinWalker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index bde76627466..94cf088f282 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -1296,7 +1296,7 @@ public override void Walk(JoinWalker walker, ref int depth) protected class NextLevelQueueEntry : QueueEntry { - public static NextLevelQueueEntry Instance = new NextLevelQueueEntry(); + public static readonly NextLevelQueueEntry Instance = new NextLevelQueueEntry(); private NextLevelQueueEntry() { From 63ac31bec218330bf8d45d59c299452124e490d5 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 21 Aug 2022 16:27:12 +0300 Subject: [PATCH 20/28] Do no use obsolete members --- src/NHibernate/Loader/JoinWalker.cs | 40 ++++++++++++----------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 94cf088f282..2ce159295a9 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -463,19 +463,6 @@ private void WalkEntityAssociationTree(IAssociationType associationType, IOuterJ /// /// Since 5.4 protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias, string path) - { - // TODO: inline function -#pragma warning disable 618 - WalkEntityTree(persister, alias, path, depth); -#pragma warning restore 618 - } - - /// - /// For an entity class, add to a list of associations to be fetched - /// by outerjoin - /// - [Obsolete("Use or override the overload without the currentDepth parameter")] - protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias, string path, int currentDepth) { int n = persister.CountSubclassProperties(); @@ -499,24 +486,21 @@ protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias } /// - /// For a component, add to a list of associations to be fetched by outerjoin + /// For an entity class, add to a list of associations to be fetched + /// by outerjoin /// - /// Since 5.4 - protected void WalkComponentTree(IAbstractComponentType componentType, int begin, string alias, string path, - ILhsAssociationTypeSqlInfo associationTypeSQLInfo) + [Obsolete("Use or override the overload without the currentDepth parameter")] + protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias, string path, int currentDepth) { - // TODO: inline function -#pragma warning disable 618 - WalkComponentTree(componentType, begin, alias, path, depth, associationTypeSQLInfo); -#pragma warning restore 618 + WalkEntityTree(persister, alias, path); } /// /// For a component, add to a list of associations to be fetched by outerjoin /// - [Obsolete("Use or override the overload without the currentDepth parameter")] + /// Since 5.4 protected void WalkComponentTree(IAbstractComponentType componentType, int begin, string alias, string path, - int currentDepth, ILhsAssociationTypeSqlInfo associationTypeSQLInfo) + ILhsAssociationTypeSqlInfo associationTypeSQLInfo) { IType[] types = componentType.Subtypes; string[] propertyNames = componentType.PropertyNames; @@ -567,6 +551,16 @@ protected void WalkComponentTree(IAbstractComponentType componentType, int begin } } + /// + /// For a component, add to a list of associations to be fetched by outerjoin + /// + [Obsolete("Use or override the overload without the currentDepth parameter")] + protected void WalkComponentTree(IAbstractComponentType componentType, int begin, string alias, string path, + int currentDepth, ILhsAssociationTypeSqlInfo associationTypeSQLInfo) + { + WalkComponentTree(componentType, begin, alias, path, associationTypeSQLInfo); + } + /// /// For a composite element, add to a list of associations to be fetched by outerjoin /// From 6adeee09ca2748aac76aaaca27a3102c00d992d4 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 21 Aug 2022 16:38:44 +0300 Subject: [PATCH 21/28] Make CodeFactor happy --- .../NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs | 1 - .../NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs | 4 ++-- src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs | 1 - .../NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs | 1 - .../NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs | 4 ++-- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs index 7d117485969..97e12483191 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs @@ -15,7 +15,6 @@ namespace NHibernate.Test.NHSpecificTest.GH2201 { using System.Threading.Tasks; - [TestFixture] public class CircularReferenceFetchFixtureAsync : BaseFetchFixture { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs index d226bb67ce8..4beeb767da4 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs @@ -45,7 +45,7 @@ protected override void OnSetUp() { var name = names[i]; - Person parent = new Person() + var parent = new Person() { Name = name, Details = new Detail() @@ -56,7 +56,7 @@ protected override void OnSetUp() for (int j = 1; j <= 3; j++) { - Person child = new Person() + var child = new Person() { Name = $"Child ${j} of ${parent.Name}", Parent = parent, diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs index d0cd85da332..779f3fbeaae 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs @@ -129,7 +129,6 @@ protected void Verify(Entity result) VerifyChildrenNotInitialized(result.AdditionalEntity); VerifyChildrenNotInitialized(result.SourceEntity); VerifyChildrenNotInitialized(result.ReferencedEntity); - } protected void VerifyChildrenInitialized(Entity result) diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs index f960eac8e5f..ef9b05969e4 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchFixture.cs @@ -4,7 +4,6 @@ namespace NHibernate.Test.NHSpecificTest.GH2201 { - [TestFixture] public class CircularReferenceFetchFixture : BaseFetchFixture { diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs index dcf841b8604..c1f48b44b73 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/DetectFetchLoopsFalseFixture.cs @@ -34,7 +34,7 @@ protected override void OnSetUp() { var name = names[i]; - Person parent = new Person() + var parent = new Person() { Name = name, Details = new Detail() @@ -45,7 +45,7 @@ protected override void OnSetUp() for (int j = 1; j <= 3; j++) { - Person child = new Person() + var child = new Person() { Name = $"Child ${j} of ${parent.Name}", Parent = parent, From 109233e9bfb5e22cdb4056bc27d56e6821b8f6bc Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 21 Aug 2022 17:16:09 +0300 Subject: [PATCH 22/28] Simplify --- src/NHibernate/Loader/JoinWalker.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 2ce159295a9..c951141906e 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -330,7 +330,7 @@ protected void ProcessJoins() while(joinQueue.Count > 0) { QueueEntry entry = joinQueue.Dequeue(); - entry.Walk(this, ref depth); + entry.Walk(this); } } @@ -1256,7 +1256,7 @@ protected static string GetSelectFragment(OuterJoinableAssociation join, string protected abstract class QueueEntry { - public abstract void Walk(JoinWalker walker, ref int depth); + public abstract void Walk(JoinWalker walker); } protected abstract class QueueEntry : QueueEntry where T : IJoinable @@ -1272,7 +1272,7 @@ protected abstract class EntityQueueEntry : QueueEntry where T : IJoinable protected class EntityQueueEntry : EntityQueueEntry { - public override void Walk(JoinWalker walker, ref int depth) + public override void Walk(JoinWalker walker) { walker.WalkEntityTree(Persister, Alias, Path); } @@ -1282,7 +1282,7 @@ protected class CollectionQueueEntry : EntityQueueEntry { public string PathAlias { get; set; } - public override void Walk(JoinWalker walker, ref int depth) + public override void Walk(JoinWalker walker) { walker.WalkCollectionTree(Persister, Alias, Path, PathAlias); } @@ -1296,9 +1296,9 @@ private NextLevelQueueEntry() { } - public override void Walk(JoinWalker walker, ref int depth) + public override void Walk(JoinWalker walker) { - depth++; + walker.depth++; } } } From 8bb4e59cf160495b8432ce8c07b84c92e62bc1d7 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 21 Aug 2022 20:32:27 +0300 Subject: [PATCH 23/28] clean up --- .../NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs | 1 - src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs | 2 +- .../NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs index da9c96f8c85..5687c0cac44 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs @@ -45,7 +45,6 @@ protected override void OnSetUp() [Test] public async Task QueryOverAsync() { - using(var logSpy = new SqlLogSpy()) using (var session = OpenSession()) { Entity e1 = null; diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs index 779f3fbeaae..8febf0ad25c 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/BaseFetchFixture.cs @@ -4,7 +4,7 @@ namespace NHibernate.Test.NHSpecificTest.GH2201 { - public class BaseFetchFixture : TestCaseMappingByCode + public abstract class BaseFetchFixture : TestCaseMappingByCode { protected int _id; protected int _depth; diff --git a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs index 5c2dc526253..68f834e4c02 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2201/CircularReferenceFetchDepthFixture.cs @@ -34,7 +34,6 @@ protected override void OnSetUp() [Test] public void QueryOver() { - using(var logSpy = new SqlLogSpy()) using (var session = OpenSession()) { Entity e1 = null; From 5043ea49e64a3c2eeb3e74d9981962b9bed53466 Mon Sep 17 00:00:00 2001 From: Alex Zaytsev Date: Sun, 21 Aug 2022 21:40:57 +0000 Subject: [PATCH 24/28] Minor --- src/NHibernate/Loader/JoinWalker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index c951141906e..3d87906517d 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -779,7 +779,7 @@ protected virtual bool IsDuplicateAssociation(string foreignKeyTable, string[] f if (detectFetchLoops) { - AssociationKey associationKey = new AssociationKey(foreignKeyColumns, foreignKeyTable); + var associationKey = new AssociationKey(foreignKeyColumns, foreignKeyTable); return !visitedAssociationKeys.Add(associationKey); } From 5b563f55c6dd554241e63c4a8155a5fcd5d3a8e7 Mon Sep 17 00:00:00 2001 From: Alex Zaytsev Date: Mon, 22 Aug 2022 11:31:48 +1200 Subject: [PATCH 25/28] Code cleanup & format --- src/NHibernate/Loader/JoinWalker.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 3d87906517d..83e0e990634 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -327,7 +327,7 @@ protected void WalkCollectionTree(IQueryableCollection persister, string alias) protected void ProcessJoins() { - while(joinQueue.Count > 0) + while (joinQueue.Count > 0) { QueueEntry entry = joinQueue.Dequeue(); entry.Walk(this); @@ -341,7 +341,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st { if (persister.IsOneToMany) { - WalkEntityTree((IOuterJoinLoadable)persister.ElementPersister, alias, path); + WalkEntityTree((IOuterJoinLoadable) persister.ElementPersister, alias, path); } else { @@ -775,15 +775,13 @@ protected virtual string GenerateRootAlias(string description) /// protected virtual bool IsDuplicateAssociation(string foreignKeyTable, string[] foreignKeyColumns) { - bool detectFetchLoops = Factory.Settings.DetectFetchLoops; - - if (detectFetchLoops) + if (!Factory.Settings.DetectFetchLoops) { - var associationKey = new AssociationKey(foreignKeyColumns, foreignKeyTable); - return !visitedAssociationKeys.Add(associationKey); + return false; } - return false; + AssociationKey associationKey = new AssociationKey(foreignKeyColumns, foreignKeyTable); + return !visitedAssociationKeys.Add(associationKey); } /// From e928c786f2e54ac2f5435695c407b6cfd6d8eb9e Mon Sep 17 00:00:00 2001 From: Alex Zaytsev Date: Mon, 22 Aug 2022 12:08:23 +1200 Subject: [PATCH 26/28] Simplify QueueEntry hierarchy --- src/NHibernate/Loader/JoinWalker.cs | 82 ++++++++++++++--------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 83e0e990634..8f1d75fd15a 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -15,7 +15,6 @@ namespace NHibernate.Loader public class JoinWalker { private readonly ISessionFactoryImplementor factory; - private Queue joinQueue = new Queue(); private int depth; protected readonly IList associations = new List(); private readonly HashSet visitedAssociationKeys = new HashSet(); @@ -33,6 +32,7 @@ public class JoinWalker private string[] aliases; private LockMode[] lockModeArray; private SqlString sql; + private readonly Queue _joinQueue = new(); public string[] CollectionSuffixes { @@ -192,26 +192,11 @@ private void AddAssociationToJoinTree(IAssociationType type, string[] aliasedLhs if (qc != null) { - joinQueue.Enqueue( - new CollectionQueueEntry() - { - Persister = qc, - Alias = subalias, - Path = path, - PathAlias = pathAlias, - } - ); + _joinQueue.Enqueue(new CollectionJoinQueueEntry(qc, subalias, path, pathAlias)); } else if (joinable is IOuterJoinLoadable jl) { - joinQueue.Enqueue( - new EntityQueueEntry() - { - Persister = jl, - Alias = subalias, - Path = path, - } - ); + _joinQueue.Enqueue(new EntityJoinQueueEntry(jl, subalias, path)); } } @@ -327,9 +312,9 @@ protected void WalkCollectionTree(IQueryableCollection persister, string alias) protected void ProcessJoins() { - while (joinQueue.Count > 0) + while (_joinQueue.Count > 0) { - QueueEntry entry = joinQueue.Dequeue(); + var entry = _joinQueue.Dequeue(); entry.Walk(this); } } @@ -466,7 +451,7 @@ protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias { int n = persister.CountSubclassProperties(); - joinQueue.Enqueue(NextLevelQueueEntry.Instance); + _joinQueue.Enqueue(NextLevelJoinQueueEntry.Instance); for (int i = 0; i < n; i++) { IType type = persister.GetSubclassPropertyType(i); @@ -1252,49 +1237,60 @@ protected static string GetSelectFragment(OuterJoinableAssociation join, string return join.GetSelectFragment(entitySuffix, collectionSuffix, next); } - protected abstract class QueueEntry + protected interface IJoinQueueEntry { - public abstract void Walk(JoinWalker walker); + void Walk(JoinWalker walker); } - protected abstract class QueueEntry : QueueEntry where T : IJoinable + protected class EntityJoinQueueEntry : IJoinQueueEntry { - public string Alias { get; set; } - public T Persister { get; set; } - } + private readonly IOuterJoinLoadable _persister; + private readonly string _alias; + private readonly string _path; - protected abstract class EntityQueueEntry : QueueEntry where T : IJoinable - { - public string Path { get; set; } - } + public EntityJoinQueueEntry(IOuterJoinLoadable persister, string alias, string path) + { + _persister = persister; + _alias = alias; + _path = path; + } - protected class EntityQueueEntry : EntityQueueEntry - { - public override void Walk(JoinWalker walker) + public void Walk(JoinWalker walker) { - walker.WalkEntityTree(Persister, Alias, Path); + walker.WalkEntityTree(_persister, _alias, _path); } } - protected class CollectionQueueEntry : EntityQueueEntry + protected class CollectionJoinQueueEntry : IJoinQueueEntry { - public string PathAlias { get; set; } + private readonly IQueryableCollection _persister; + private readonly string _alias; + private readonly string _path; + private readonly string _pathAlias; + + public CollectionJoinQueueEntry(IQueryableCollection persister, string alias, string path, string pathAlias) + { + _persister = persister; + _alias = alias; + _path = path; + _pathAlias = pathAlias; + } - public override void Walk(JoinWalker walker) + public void Walk(JoinWalker walker) { - walker.WalkCollectionTree(Persister, Alias, Path, PathAlias); + walker.WalkCollectionTree(_persister, _alias, _path, _pathAlias); } } - protected class NextLevelQueueEntry : QueueEntry + protected class NextLevelJoinQueueEntry : IJoinQueueEntry { - public static readonly NextLevelQueueEntry Instance = new NextLevelQueueEntry(); + public static readonly NextLevelJoinQueueEntry Instance = new(); - private NextLevelQueueEntry() + private NextLevelJoinQueueEntry() { } - public override void Walk(JoinWalker walker) + public void Walk(JoinWalker walker) { walker.depth++; } From 4a93c134397fce3964fa3e1723fd87fbadef2b46 Mon Sep 17 00:00:00 2001 From: Alex Zaytsev Date: Mon, 22 Aug 2022 12:22:31 +1200 Subject: [PATCH 27/28] Rename depth to _depth --- src/NHibernate/Loader/JoinWalker.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 8f1d75fd15a..47147531603 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -15,7 +15,6 @@ namespace NHibernate.Loader public class JoinWalker { private readonly ISessionFactoryImplementor factory; - private int depth; protected readonly IList associations = new List(); private readonly HashSet visitedAssociationKeys = new HashSet(); private readonly IDictionary enabledFilters; @@ -33,6 +32,7 @@ public class JoinWalker private LockMode[] lockModeArray; private SqlString sql; private readonly Queue _joinQueue = new(); + private int _depth; public string[] CollectionSuffixes { @@ -343,7 +343,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st // if the current depth is 0, the root thing being loaded is the // many-to-many collection itself. Here, it is alright to use // an inner join... - bool useInnerJoin = depth == 0; + bool useInnerJoin = _depth == 0; var joinType = GetJoinType( @@ -354,7 +354,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st persister.TableName, lhsColumns, !useInnerJoin, - depth - 1, + _depth - 1, null); AddAssociationToJoinTreeIfNecessary( @@ -384,7 +384,7 @@ internal void AddExplicitEntityJoinAssociation( string path, string pathAlias) { - depth = 0; + _depth = 0; visitedAssociationKeys.Clear(); OuterJoinableAssociation assoc = InitAssociation(new OuterJoinableAssociation( @@ -429,7 +429,7 @@ private void WalkEntityAssociationTree(IAssociationType associationType, IOuterJ lhsTable, lhsColumns, nullable, - depth, + _depth, persister.GetCascadeStyle(propertyNumber)); AddAssociationToJoinTreeIfNecessary( @@ -514,7 +514,7 @@ protected void WalkComponentTree(IAbstractComponentType componentType, int begin lhsTable, lhsColumns, propertyNullability == null || propertyNullability[i], - depth, + _depth, componentType.GetCascadeStyle(i)); AddAssociationToJoinTreeIfNecessary( @@ -582,7 +582,7 @@ private void WalkCompositeElementTree(IAbstractComponentType compositeType, stri persister.TableName, lhsColumns, propertyNullability == null || propertyNullability[i], - depth, + _depth, compositeType.GetCascadeStyle(i)); AddAssociationToJoinTreeIfNecessary( @@ -1292,7 +1292,7 @@ private NextLevelJoinQueueEntry() public void Walk(JoinWalker walker) { - walker.depth++; + walker._depth++; } } } From 7ce6da33f5525c1b015abcf7712007fb633042d1 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 22 Aug 2022 09:01:29 +0300 Subject: [PATCH 28/28] Marked Obsolete members instead of new one --- src/NHibernate/Loader/JoinWalker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 47147531603..731ee2cc670 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -446,7 +446,6 @@ private void WalkEntityAssociationTree(IAssociationType associationType, IOuterJ /// For an entity class, add to a list of associations to be fetched /// by outerjoin /// - /// Since 5.4 protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias, string path) { int n = persister.CountSubclassProperties(); @@ -474,6 +473,7 @@ protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias /// For an entity class, add to a list of associations to be fetched /// by outerjoin /// + // Since 5.4 [Obsolete("Use or override the overload without the currentDepth parameter")] protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias, string path, int currentDepth) { @@ -483,7 +483,6 @@ protected virtual void WalkEntityTree(IOuterJoinLoadable persister, string alias /// /// For a component, add to a list of associations to be fetched by outerjoin /// - /// Since 5.4 protected void WalkComponentTree(IAbstractComponentType componentType, int begin, string alias, string path, ILhsAssociationTypeSqlInfo associationTypeSQLInfo) { @@ -539,6 +538,7 @@ protected void WalkComponentTree(IAbstractComponentType componentType, int begin /// /// For a component, add to a list of associations to be fetched by outerjoin /// + // Since 5.4 [Obsolete("Use or override the overload without the currentDepth parameter")] protected void WalkComponentTree(IAbstractComponentType componentType, int begin, string alias, string path, int currentDepth, ILhsAssociationTypeSqlInfo associationTypeSQLInfo)