From 2af903563e063388bf2fede83420ab99a8d4bb9e Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 27 Jan 2019 14:09:01 +0200 Subject: [PATCH 1/9] test case --- .../NHSpecificTest/GH1994/Entity.cs | 22 ++++++ .../NHSpecificTest/GH1994/Fixture.cs | 73 +++++++++++++++++++ .../NHSpecificTest/GH1994/Mappings.hbm.xml | 35 +++++++++ 3 files changed, 130 insertions(+) create mode 100644 src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs new file mode 100644 index 00000000000..492fe4c1964 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH1994 +{ + public class Base + { + public virtual Guid Key { get; set; } + + public virtual bool IsDeleted { get; set; } + } + + public class Asset : Base + { + public virtual ISet Documents { get; set; } = new HashSet(); + } + + public class Document : Base + { + public virtual ISet Assets { get; set; } = new HashSet(); + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs new file mode 100644 index 00000000000..11a1bed0a20 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs @@ -0,0 +1,73 @@ +using System.Linq; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1994 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var a = new Asset(); + var d = new Document { IsDeleted = true }; + a.Documents.Add(d); + + session.Save(a); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // The HQL delete does all the job inside the database without loading the entities, but it does + // not handle delete order for avoiding violating constraints if any. Use + // session.Delete("from System.Object"); + // instead if in need of having NHbernate ordering the deletes, but this will cause + // loading the entities in the session. + + session.Delete("from System.Object"); + + transaction.Commit(); + } + } + + [Test] + public void YourTestName() + { + using (var s = OpenSession()) + { + var assetsUnfiltered = s.Query() + .FetchMany(x => x.Documents) + .ToList(); + + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + s.Clear(); + var assetsFilteredQuery = s.Query() + .FetchMany(x => x.Documents) + .ToList(); + + s.Clear(); + var assetsFilteredQueryOver = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Documents) + .List(); + + Assert.That(assetsUnfiltered.Count, Is.EqualTo(1), "unfiltered assets"); + Assert.That(assetsUnfiltered[0].Documents.Count, Is.EqualTo(1), "unfiltered asset documents"); + + Assert.That(assetsFilteredQueryOver.Count, Is.EqualTo(1), " query over filtered assets"); + Assert.That(assetsFilteredQueryOver[0].Documents.Count, Is.EqualTo(0), "query over filtered asset documents"); + + Assert.That(assetsFilteredQuery.Count, Is.EqualTo(1), "query filtered assets"); + Assert.That(assetsFilteredQuery[0].Documents.Count, Is.EqualTo(0), "query filtered asset documents"); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml new file mode 100644 index 00000000000..b91a3c1811c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 101483cb2e94af199c77f5d6c1941d5409ff55f4 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Mon, 28 Jan 2019 10:04:11 +1300 Subject: [PATCH 2/9] Make tests independent --- .../NHSpecificTest/GH1994/Fixture.cs | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs index 11a1bed0a20..a384eb5f273 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs @@ -39,7 +39,7 @@ protected override void OnTearDown() } [Test] - public void YourTestName() + public void TestUnfilteredLinqQuery() { using (var s = OpenSession()) { @@ -47,26 +47,40 @@ public void YourTestName() .FetchMany(x => x.Documents) .ToList(); + Assert.That(assetsUnfiltered.Count, Is.EqualTo(1), "unfiltered assets"); + Assert.That(assetsUnfiltered[0].Documents.Count, Is.EqualTo(1), "unfiltered asset documents"); + } + } + + [Test] + public void TestFilteredLinqQuery() + { + using (var s = OpenSession()) + { s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); - s.Clear(); var assetsFilteredQuery = s.Query() .FetchMany(x => x.Documents) .ToList(); - s.Clear(); - var assetsFilteredQueryOver = s.QueryOver() - .Fetch(SelectMode.Fetch, x => x.Documents) - .List(); + Assert.That(assetsFilteredQuery.Count, Is.EqualTo(1), "query filtered assets"); + Assert.That(assetsFilteredQuery[0].Documents.Count, Is.EqualTo(0), "query filtered asset documents"); + } + } - Assert.That(assetsUnfiltered.Count, Is.EqualTo(1), "unfiltered assets"); - Assert.That(assetsUnfiltered[0].Documents.Count, Is.EqualTo(1), "unfiltered asset documents"); + [Test] + public void TestFilteredQueryOver() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + var assetsFilteredQueryOver = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Documents) + .List(); Assert.That(assetsFilteredQueryOver.Count, Is.EqualTo(1), " query over filtered assets"); Assert.That(assetsFilteredQueryOver[0].Documents.Count, Is.EqualTo(0), "query over filtered asset documents"); - - Assert.That(assetsFilteredQuery.Count, Is.EqualTo(1), "query filtered assets"); - Assert.That(assetsFilteredQuery[0].Documents.Count, Is.EqualTo(0), "query filtered asset documents"); } } } From d166415ee937c0e43ff133a55790f22e3129bf6b Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Mon, 28 Jan 2019 12:13:12 +1300 Subject: [PATCH 3/9] Add mixed documents --- .../NHSpecificTest/GH1994/Fixture.cs | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs index a384eb5f273..f2b9f352ba7 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs @@ -1,5 +1,6 @@ using System.Linq; using NHibernate.Linq; +using NHibernate.Transform; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.GH1994 @@ -13,8 +14,8 @@ protected override void OnSetUp() using (var transaction = session.BeginTransaction()) { var a = new Asset(); - var d = new Document { IsDeleted = true }; - a.Documents.Add(d); + a.Documents.Add(new Document { IsDeleted = true }); + a.Documents.Add(new Document { IsDeleted = false }); session.Save(a); transaction.Commit(); @@ -43,12 +44,12 @@ public void TestUnfilteredLinqQuery() { using (var s = OpenSession()) { - var assetsUnfiltered = s.Query() - .FetchMany(x => x.Documents) - .ToList(); + var query = s.Query() + .FetchMany(x => x.Documents) + .ToList(); - Assert.That(assetsUnfiltered.Count, Is.EqualTo(1), "unfiltered assets"); - Assert.That(assetsUnfiltered[0].Documents.Count, Is.EqualTo(1), "unfiltered asset documents"); + Assert.That(query.Count, Is.EqualTo(1), "unfiltered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(2), "unfiltered asset documents"); } } @@ -58,13 +59,12 @@ public void TestFilteredLinqQuery() using (var s = OpenSession()) { s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + var query = s.Query() + .FetchMany(x => x.Documents) + .ToList(); - var assetsFilteredQuery = s.Query() - .FetchMany(x => x.Documents) - .ToList(); - - Assert.That(assetsFilteredQuery.Count, Is.EqualTo(1), "query filtered assets"); - Assert.That(assetsFilteredQuery[0].Documents.Count, Is.EqualTo(0), "query filtered asset documents"); + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); } } @@ -75,12 +75,13 @@ public void TestFilteredQueryOver() { s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); - var assetsFilteredQueryOver = s.QueryOver() - .Fetch(SelectMode.Fetch, x => x.Documents) - .List(); + var query = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Documents) + .TransformUsing(Transformers.DistinctRootEntity) + .List(); - Assert.That(assetsFilteredQueryOver.Count, Is.EqualTo(1), " query over filtered assets"); - Assert.That(assetsFilteredQueryOver[0].Documents.Count, Is.EqualTo(0), "query over filtered asset documents"); + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); } } } From b40c11f819f0fb38fe648ceb03636bf840829671 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 25 Apr 2019 17:16:41 +0300 Subject: [PATCH 4/9] Use entity aliases for collection persister to support many-to-many filtering Fixes #1994 --- .../Async/NHSpecificTest/GH1994/Fixture.cs | 99 +++++++++++++++++++ .../Collection/AbstractCollectionPersister.cs | 1 + src/NHibernate/Loader/BasicLoader.cs | 5 +- .../Loader/GeneratedCollectionAliases.cs | 10 +- src/NHibernate/Loader/Hql/QueryLoader.cs | 23 +++++ .../Collection/AbstractCollectionPersister.cs | 25 +++-- .../Collection/ICollectionPersister.cs | 9 ++ 7 files changed, 159 insertions(+), 13 deletions(-) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs new file mode 100644 index 00000000000..e051783d208 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------ +// +// 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.Linq; +using NHibernate.Linq; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1994 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var a = new Asset(); + a.Documents.Add(new Document { IsDeleted = true }); + a.Documents.Add(new Document { IsDeleted = false }); + + session.Save(a); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // The HQL delete does all the job inside the database without loading the entities, but it does + // not handle delete order for avoiding violating constraints if any. Use + // session.Delete("from System.Object"); + // instead if in need of having NHbernate ordering the deletes, but this will cause + // loading the entities in the session. + + session.Delete("from System.Object"); + + transaction.Commit(); + } + } + + [Test] + public async Task TestUnfilteredLinqQueryAsync() + { + using (var s = OpenSession()) + { + var query = await (s.Query() + .FetchMany(x => x.Documents) + .ToListAsync()); + + Assert.That(query.Count, Is.EqualTo(1), "unfiltered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(2), "unfiltered asset documents"); + } + } + + [Test] + public async Task TestFilteredLinqQueryAsync() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + var query = await (s.Query() + .FetchMany(x => x.Documents) + .ToListAsync()); + + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); + } + } + + [Test] + public async Task TestFilteredQueryOverAsync() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + var query = await (s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Documents) + .TransformUsing(Transformers.DistinctRootEntity) + .ListAsync()); + + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); + } + } + } +} diff --git a/src/NHibernate/Async/Persister/Collection/AbstractCollectionPersister.cs b/src/NHibernate/Async/Persister/Collection/AbstractCollectionPersister.cs index eb15d667603..8577bab32a2 100644 --- a/src/NHibernate/Async/Persister/Collection/AbstractCollectionPersister.cs +++ b/src/NHibernate/Async/Persister/Collection/AbstractCollectionPersister.cs @@ -39,6 +39,7 @@ namespace NHibernate.Persister.Collection { using System.Threading.Tasks; using System.Threading; + public abstract partial class AbstractCollectionPersister : ICollectionMetadata, ISqlLoadableCollection, IPostInsertIdentityPersister, ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister { diff --git a/src/NHibernate/Loader/BasicLoader.cs b/src/NHibernate/Loader/BasicLoader.cs index 2d3ddafd877..3de1852dfec 100644 --- a/src/NHibernate/Loader/BasicLoader.cs +++ b/src/NHibernate/Loader/BasicLoader.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using NHibernate.Engine; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; @@ -27,7 +28,7 @@ protected override sealed ICollectionAliases[] CollectionAliases protected abstract string[] Suffixes { get; } protected abstract string[] CollectionSuffixes { get; } - + protected virtual Dictionary[] CollectionUserProvidedAliases => null; protected override void PostInstantiate() { ILoadable[] persisters = EntityPersisters; @@ -50,7 +51,7 @@ protected override void PostInstantiate() { bagCount++; } - collectionDescriptors[i] = new GeneratedCollectionAliases(collectionPersisters[i], collectionSuffixes[i]); + collectionDescriptors[i] = new GeneratedCollectionAliases(CollectionUserProvidedAliases?[i] ?? CollectionHelper.EmptyDictionary(), collectionPersisters[i], collectionSuffixes[i]); } } else diff --git a/src/NHibernate/Loader/GeneratedCollectionAliases.cs b/src/NHibernate/Loader/GeneratedCollectionAliases.cs index 71b127fb60e..4dc00e229fe 100644 --- a/src/NHibernate/Loader/GeneratedCollectionAliases.cs +++ b/src/NHibernate/Loader/GeneratedCollectionAliases.cs @@ -24,17 +24,17 @@ public GeneratedCollectionAliases(IDictionary userProvidedAlia this.suffix = suffix; this.userProvidedAliases = userProvidedAliases; - keyAliases = GetUserProvidedAliases("key", persister.GetKeyColumnAliases(suffix)); + keyAliases = GetUserProvidedAliases(CollectionPersister.PropKey, persister.GetKeyColumnAliases(suffix)); - indexAliases = GetUserProvidedAliases("index", persister.GetIndexColumnAliases(suffix)); + indexAliases = GetUserProvidedAliases(CollectionPersister.PropIndex, persister.GetIndexColumnAliases(suffix)); // NH-1612: Add aliases for all composite element properties to support access // to individual composite element properties in elements. elementAliases = persister.ElementType.IsComponentType ? GetUserProvidedCompositeElementAliases(persister.GetElementColumnAliases(suffix)) - : GetUserProvidedAliases("element", persister.GetElementColumnAliases(suffix)); + : GetUserProvidedAliases(CollectionPersister.PropElement, persister.GetElementColumnAliases(suffix)); - identifierAlias = GetUserProvidedAlias("id", persister.GetIdentifierColumnAlias(suffix)); + identifierAlias = GetUserProvidedAlias(CollectionPersister.PropId, persister.GetIdentifierColumnAlias(suffix)); } public GeneratedCollectionAliases(ICollectionPersister persister, string str) @@ -45,7 +45,7 @@ private string[] GetUserProvidedCompositeElementAliases(string[] defaultAliases) var aliases = new List(); foreach (KeyValuePair userProvidedAlias in userProvidedAliases) { - if (userProvidedAlias.Key.StartsWith("element.", StringComparison.Ordinal)) + if (userProvidedAlias.Key.StartsWith(CollectionPersister.PropElement + ".", StringComparison.Ordinal)) { aliases.AddRange(userProvidedAlias.Value); } diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs index 9bb71eb2d3b..5553b657647 100644 --- a/src/NHibernate/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Loader/Hql/QueryLoader.cs @@ -46,6 +46,7 @@ public partial class QueryLoader : BasicLoader private LockMode[] _defaultLockModes; private IType[] _cacheTypes; private ISet _uncacheableCollectionPersisters; + private Dictionary[] _collectionUserProvidedAliases; public QueryLoader(QueryTranslatorImpl queryTranslator, ISessionFactoryImplementor factory, SelectClause selectClause) : base(factory) @@ -205,6 +206,11 @@ protected override ICollectionPersister[] CollectionPersisters public override IType[] CacheTypes => _cacheTypes; + protected override Dictionary[] CollectionUserProvidedAliases + { + get { return _collectionUserProvidedAliases; } + } + private void Initialize(SelectClause selectClause) { IList fromElementList = selectClause.FromElementsForLoad; @@ -225,6 +231,8 @@ private void Initialize(SelectClause selectClause) _collectionOwners = new int[length]; _collectionSuffixes = new string[length]; CollectionFetches = new bool[length]; + if (collectionFromElements.Any(qc => qc.QueryableCollection.IsManyToMany)) + _collectionUserProvidedAliases = new Dictionary[length]; for (int i = 0; i < length; i++) { @@ -271,6 +279,7 @@ private void Initialize(SelectClause selectClause) // TODO should we just collect these like with the collections above? _sqlAliasSuffixes[i] = (size == 1) ? "" : i + "_"; // sqlAliasSuffixes[i] = element.getColumnAliasSuffix(); + _includeInSelect[i] = !element.IsFetch; EntityFetches[i] = element.IsFetch; if (element.IsFetch) @@ -282,6 +291,20 @@ private void Initialize(SelectClause selectClause) _selectLength++; } + if (element.IsFetch && element.QueryableCollection?.IsManyToMany == true && element.QueryableCollection.IsAffectedByEnabledFilters(_queryTranslator.EnabledFilters)) + { + var collectionIndex = collectionFromElements.IndexOf(element); + + if (collectionIndex >= 0) + { + // See test TestFilteredLinqQuery to see why this replacement is needed + CollectionUserProvidedAliases[collectionIndex] = new Dictionary + { + {CollectionPersister.PropElement, _entityPersisters[i].GetIdentifierAliases(Suffixes[i]) } + }; + } + } + _owners[i] = -1; //by default if (element.IsFetch) { diff --git a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs index 4489945d400..2433297107d 100644 --- a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs @@ -27,6 +27,15 @@ namespace NHibernate.Persister.Collection { + internal static class CollectionPersister + { + /// The property name of the "special" identifier property + public const string PropId = "id"; + public const string PropElement = "element"; + public const string PropKey = "key"; + public const string PropIndex = "index"; + } + /// /// Summary description for AbstractCollectionPersister. /// @@ -1487,10 +1496,14 @@ public override string ToString() } public bool IsAffectedByEnabledFilters(ISessionImplementor session) + { + return IsAffectedByEnabledFilters(session.EnabledFilters); + } + public bool IsAffectedByEnabledFilters(IDictionary enabledFilters) { return - filterHelper.IsAffectedBy(session.EnabledFilters) - || (IsManyToMany && manyToManyFilterHelper.IsAffectedBy(session.EnabledFilters)); +filterHelper.IsAffectedBy(enabledFilters) + || (IsManyToMany && manyToManyFilterHelper.IsAffectedBy(enabledFilters)); } public string[] GetCollectionPropertyColumnAliases(string propertyName, string suffix) @@ -1512,17 +1525,17 @@ public string[] GetCollectionPropertyColumnAliases(string propertyName, string s public void InitCollectionPropertyMap() { - InitCollectionPropertyMap("key", keyType, keyColumnAliases, keyColumnNames); - InitCollectionPropertyMap("element", elementType, elementColumnAliases, elementColumnNames); + InitCollectionPropertyMap(CollectionPersister.PropKey, keyType, keyColumnAliases, keyColumnNames); + InitCollectionPropertyMap(CollectionPersister.PropElement, elementType, elementColumnAliases, elementColumnNames); if (hasIndex) { - InitCollectionPropertyMap("index", indexType, indexColumnAliases, indexColumnNames); + InitCollectionPropertyMap(CollectionPersister.PropIndex, indexType, indexColumnAliases, indexColumnNames); } if (hasIdentifier) { - InitCollectionPropertyMap("id", identifierType, new string[] {identifierColumnAlias}, + InitCollectionPropertyMap(CollectionPersister.PropId, identifierType, new string[] {identifierColumnAlias}, new string[] {identifierColumnName}); } } diff --git a/src/NHibernate/Persister/Collection/ICollectionPersister.cs b/src/NHibernate/Persister/Collection/ICollectionPersister.cs index 4d87ea158b3..83fcf6a0660 100644 --- a/src/NHibernate/Persister/Collection/ICollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/ICollectionPersister.cs @@ -285,6 +285,7 @@ public partial interface ICollectionPersister object NotFoundObject { get; } } + //6.0 TODO: Merge into ICollectionPersister public static class CollectionPersisterExtensions { /// @@ -304,5 +305,13 @@ public static int GetBatchSize(this ICollectionPersister persister) return 1; } + internal static bool IsAffectedByEnabledFilters(this ICollectionPersister persister, IDictionary enabledFilters) + { + if (persister is AbstractCollectionPersister acp) + { + return acp.IsAffectedByEnabledFilters(enabledFilters); + } + return false; + } } } From 3471e2cc820bd4fae7094ec0c54f89b64bec898b Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sat, 27 Apr 2019 20:57:53 +0300 Subject: [PATCH 5/9] Also handle case when many-to-many collection is filtered by where clause --- .../Async/NHSpecificTest/GH0000/Fixture.cs | 2 +- .../Async/NHSpecificTest/GH1994/Fixture.cs | 16 +++++++++++++++- .../NHSpecificTest/GH0000/Fixture.cs | 2 +- .../NHSpecificTest/GH1994/Entity.cs | 1 + .../NHSpecificTest/GH1994/Fixture.cs | 16 +++++++++++++++- .../NHSpecificTest/GH1994/Mappings.hbm.xml | 4 ++++ src/NHibernate/Loader/Hql/QueryLoader.cs | 10 +++++++--- .../Collection/AbstractCollectionPersister.cs | 13 +++++++------ .../Persister/Collection/ICollectionPersister.cs | 11 ++++++++--- 9 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH0000/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH0000/Fixture.cs index 9e6d31db053..29c4aabe5cc 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH0000/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH0000/Fixture.cs @@ -41,7 +41,7 @@ protected override void OnTearDown() // The HQL delete does all the job inside the database without loading the entities, but it does // not handle delete order for avoiding violating constraints if any. Use // session.Delete("from System.Object"); - // instead if in need of having NHbernate ordering the deletes, but this will cause + // instead if in need of having NHibernate ordering the deletes, but this will cause // loading the entities in the session. session.CreateQuery("delete from System.Object").ExecuteUpdate(); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs index e051783d208..28924e9f136 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs @@ -41,7 +41,7 @@ protected override void OnTearDown() // The HQL delete does all the job inside the database without loading the entities, but it does // not handle delete order for avoiding violating constraints if any. Use // session.Delete("from System.Object"); - // instead if in need of having NHbernate ordering the deletes, but this will cause + // instead if in need of having NHibernate ordering the deletes, but this will cause // loading the entities in the session. session.Delete("from System.Object"); @@ -64,6 +64,20 @@ public async Task TestUnfilteredLinqQueryAsync() } } + [Test] + public async Task TestFilteredByWhereCollectionLinqQueryAsync() + { + using (var s = OpenSession()) + { + var query = await (s.Query() + .FetchMany(x => x.DocumentsFiltered) + .ToListAsync()); + + Assert.That(query.Count, Is.EqualTo(1), "unfiltered assets"); + Assert.That(query[0].DocumentsFiltered.Count, Is.EqualTo(1), "unfiltered asset documents"); + } + } + [Test] public async Task TestFilteredLinqQueryAsync() { diff --git a/src/NHibernate.Test/NHSpecificTest/GH0000/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH0000/Fixture.cs index 4a8ce621f17..58c728c7028 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0000/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0000/Fixture.cs @@ -29,7 +29,7 @@ protected override void OnTearDown() // The HQL delete does all the job inside the database without loading the entities, but it does // not handle delete order for avoiding violating constraints if any. Use // session.Delete("from System.Object"); - // instead if in need of having NHbernate ordering the deletes, but this will cause + // instead if in need of having NHibernate ordering the deletes, but this will cause // loading the entities in the session. session.CreateQuery("delete from System.Object").ExecuteUpdate(); diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs index 492fe4c1964..44f9719be0b 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs @@ -13,6 +13,7 @@ public class Base public class Asset : Base { public virtual ISet Documents { get; set; } = new HashSet(); + public virtual ISet DocumentsFiltered { get; set; } = new HashSet(); } public class Document : Base diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs index f2b9f352ba7..d084b0466dd 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs @@ -30,7 +30,7 @@ protected override void OnTearDown() // The HQL delete does all the job inside the database without loading the entities, but it does // not handle delete order for avoiding violating constraints if any. Use // session.Delete("from System.Object"); - // instead if in need of having NHbernate ordering the deletes, but this will cause + // instead if in need of having NHibernate ordering the deletes, but this will cause // loading the entities in the session. session.Delete("from System.Object"); @@ -53,6 +53,20 @@ public void TestUnfilteredLinqQuery() } } + [Test] + public void TestFilteredByWhereCollectionLinqQuery() + { + using (var s = OpenSession()) + { + var query = s.Query() + .FetchMany(x => x.DocumentsFiltered) + .ToList(); + + Assert.That(query.Count, Is.EqualTo(1), "unfiltered assets"); + Assert.That(query[0].DocumentsFiltered.Count, Is.EqualTo(1), "unfiltered asset documents"); + } + } + [Test] public void TestFilteredLinqQuery() { diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml index b91a3c1811c..16cf605e748 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml @@ -18,6 +18,10 @@ + + + + diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs index 5553b657647..e24eb1318c8 100644 --- a/src/NHibernate/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Loader/Hql/QueryLoader.cs @@ -291,16 +291,20 @@ private void Initialize(SelectClause selectClause) _selectLength++; } - if (element.IsFetch && element.QueryableCollection?.IsManyToMany == true && element.QueryableCollection.IsAffectedByEnabledFilters(_queryTranslator.EnabledFilters)) + if (collectionFromElements != null && element.IsFetch && element.QueryableCollection?.IsManyToMany == true + && element.QueryableCollection.IsManyToManyFiltered(_queryTranslator.EnabledFilters)) { var collectionIndex = collectionFromElements.IndexOf(element); if (collectionIndex >= 0) { - // See test TestFilteredLinqQuery to see why this replacement is needed + // When many-to-many is filtered we need to populate collection from element persister and not from bridge table. + // As bridge table will contain not-null values for filtered elements + // So do alias substitution for collection persister with element persister + // See test TestFilteredLinqQuery for details CollectionUserProvidedAliases[collectionIndex] = new Dictionary { - {CollectionPersister.PropElement, _entityPersisters[i].GetIdentifierAliases(Suffixes[i]) } + {CollectionPersister.PropElement, _entityPersisters[i].GetIdentifierAliases(Suffixes[i])} }; } } diff --git a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs index 2433297107d..aa9b8a89073 100644 --- a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs @@ -1389,6 +1389,11 @@ public string GetManyToManyFilterFragment(string alias, IDictionary enabledFilters) + { + return IsManyToMany && (manyToManyWhereString != null || manyToManyFilterHelper.IsAffectedBy(enabledFilters)); + } public string[] ToColumns(string alias, string propertyName) { @@ -1496,14 +1501,10 @@ public override string ToString() } public bool IsAffectedByEnabledFilters(ISessionImplementor session) - { - return IsAffectedByEnabledFilters(session.EnabledFilters); - } - public bool IsAffectedByEnabledFilters(IDictionary enabledFilters) { return -filterHelper.IsAffectedBy(enabledFilters) - || (IsManyToMany && manyToManyFilterHelper.IsAffectedBy(enabledFilters)); + filterHelper.IsAffectedBy(session.EnabledFilters) + || (IsManyToMany && manyToManyFilterHelper.IsAffectedBy(session.EnabledFilters)); } public string[] GetCollectionPropertyColumnAliases(string propertyName, string suffix) diff --git a/src/NHibernate/Persister/Collection/ICollectionPersister.cs b/src/NHibernate/Persister/Collection/ICollectionPersister.cs index 83fcf6a0660..88d2401ae2d 100644 --- a/src/NHibernate/Persister/Collection/ICollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/ICollectionPersister.cs @@ -305,13 +305,18 @@ public static int GetBatchSize(this ICollectionPersister persister) return 1; } - internal static bool IsAffectedByEnabledFilters(this ICollectionPersister persister, IDictionary enabledFilters) + + /// + /// Is this persister has enabled many-to-many filter or has many-to-many where clause + /// + internal static bool IsManyToManyFiltered(this ICollectionPersister persister, IDictionary enabledFilters) { if (persister is AbstractCollectionPersister acp) { - return acp.IsAffectedByEnabledFilters(enabledFilters); + return acp.IsManyToManyFiltered(enabledFilters); } - return false; + + return persister.IsManyToMany && !string.IsNullOrEmpty(persister.GetManyToManyFilterFragment("x", enabledFilters)); } } } From d8b4cdb7d084d67d99392b5e89c60e892e1386f8 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sat, 27 Apr 2019 21:07:50 +0300 Subject: [PATCH 6/9] Undo whitespace change --- src/NHibernate/Loader/Hql/QueryLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs index e24eb1318c8..86308f501d2 100644 --- a/src/NHibernate/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Loader/Hql/QueryLoader.cs @@ -279,7 +279,6 @@ private void Initialize(SelectClause selectClause) // TODO should we just collect these like with the collections above? _sqlAliasSuffixes[i] = (size == 1) ? "" : i + "_"; // sqlAliasSuffixes[i] = element.getColumnAliasSuffix(); - _includeInSelect[i] = !element.IsFetch; EntityFetches[i] = element.IsFetch; if (element.IsFetch) From 17a959ca605ea27e0ac4b707b2d78340edbab840 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sat, 27 Apr 2019 23:11:36 +0300 Subject: [PATCH 7/9] Fix Postgres test --- src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs | 4 ++++ src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs index 28924e9f136..34b02db20e2 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs @@ -9,6 +9,7 @@ using System.Linq; +using NHibernate.Dialect; using NHibernate.Linq; using NHibernate.Transform; using NUnit.Framework; @@ -67,6 +68,9 @@ public async Task TestUnfilteredLinqQueryAsync() [Test] public async Task TestFilteredByWhereCollectionLinqQueryAsync() { + if(Dialect is PostgreSQLDialect) + Assert.Ignore("Dialect doesn't support 0/1 to bool implicit cast"); + using (var s = OpenSession()) { var query = await (s.Query() diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs index d084b0466dd..aa0835d9a17 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs @@ -1,4 +1,5 @@ using System.Linq; +using NHibernate.Dialect; using NHibernate.Linq; using NHibernate.Transform; using NUnit.Framework; @@ -56,6 +57,9 @@ public void TestUnfilteredLinqQuery() [Test] public void TestFilteredByWhereCollectionLinqQuery() { + if(Dialect is PostgreSQLDialect) + Assert.Ignore("Dialect doesn't support 0/1 to bool implicit cast"); + using (var s = OpenSession()) { var query = s.Query() From 15aeac89f7c68e7c39f31d3e5e3ea16f0d53ee9d Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sun, 28 Apr 2019 07:36:25 +0300 Subject: [PATCH 8/9] Fix mapping --- src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml index 16cf605e748..57a58acefa8 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml @@ -18,7 +18,7 @@ - + From 6aa43fb7997263bb2d2b39ad40dce6a3451cb6b3 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Fri, 31 May 2019 15:25:18 +1200 Subject: [PATCH 9/9] Refactoring --- src/NHibernate/Loader/BasicLoader.cs | 9 ++- .../Loader/Custom/ColumnCollectionAliases.cs | 38 ++++------- .../Loader/GeneratedCollectionAliases.cs | 65 ++++++++----------- src/NHibernate/Loader/Hql/QueryLoader.cs | 6 +- 4 files changed, 50 insertions(+), 68 deletions(-) diff --git a/src/NHibernate/Loader/BasicLoader.cs b/src/NHibernate/Loader/BasicLoader.cs index 3de1852dfec..97c0781da4b 100644 --- a/src/NHibernate/Loader/BasicLoader.cs +++ b/src/NHibernate/Loader/BasicLoader.cs @@ -28,7 +28,7 @@ protected override sealed ICollectionAliases[] CollectionAliases protected abstract string[] Suffixes { get; } protected abstract string[] CollectionSuffixes { get; } - protected virtual Dictionary[] CollectionUserProvidedAliases => null; + protected override void PostInstantiate() { ILoadable[] persisters = EntityPersisters; @@ -51,7 +51,7 @@ protected override void PostInstantiate() { bagCount++; } - collectionDescriptors[i] = new GeneratedCollectionAliases(CollectionUserProvidedAliases?[i] ?? CollectionHelper.EmptyDictionary(), collectionPersisters[i], collectionSuffixes[i]); + collectionDescriptors[i] = new GeneratedCollectionAliases(GetCollectionUserProvidedAlias(i), collectionPersisters[i], collectionSuffixes[i]); } } else @@ -67,6 +67,11 @@ protected override void PostInstantiate() } } + protected virtual IDictionary GetCollectionUserProvidedAlias(int index) + { + return null; + } + private static bool IsBag(ICollectionPersister collectionPersister) { var type = collectionPersister.CollectionType.GetType(); diff --git a/src/NHibernate/Loader/Custom/ColumnCollectionAliases.cs b/src/NHibernate/Loader/Custom/ColumnCollectionAliases.cs index 1b8a7f2f2d5..3ff5fd9a3ba 100644 --- a/src/NHibernate/Loader/Custom/ColumnCollectionAliases.cs +++ b/src/NHibernate/Loader/Custom/ColumnCollectionAliases.cs @@ -1,7 +1,5 @@ -using System.Collections; using System.Collections.Generic; using NHibernate.Persister.Collection; -using NHibernate.Util; namespace NHibernate.Loader.Custom { @@ -15,19 +13,13 @@ public class ColumnCollectionAliases : ICollectionAliases private readonly string[] indexAliases; private readonly string[] elementAliases; private readonly string identifierAlias; - private readonly IDictionary userProvidedAliases; public ColumnCollectionAliases(IDictionary userProvidedAliases, ISqlLoadableCollection persister) { - this.userProvidedAliases = userProvidedAliases; - - keyAliases = GetUserProvidedAliases("key", persister.KeyColumnNames); - - indexAliases = GetUserProvidedAliases("index", persister.IndexColumnNames); - - elementAliases = GetUserProvidedAliases("element", persister.ElementColumnNames); - - identifierAlias = GetUserProvidedAlias("id", persister.IdentifierColumnName); + keyAliases = GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropKey, persister.KeyColumnNames); + indexAliases = GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropIndex, persister.IndexColumnNames); + elementAliases = GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropElement, persister.ElementColumnNames); + identifierAlias = GetUserProvidedAlias(userProvidedAliases, CollectionPersister.PropId, persister.IdentifierColumnName); } /// @@ -90,30 +82,24 @@ private static string Join(string[] aliases) : string.Join(", ", aliases); } - private string[] GetUserProvidedAliases(string propertyPath, string[] defaultAliases) + private static string[] GetUserProvidedAliases(IDictionary userProvidedAliases, string propertyPath, string[] defaultAliases) { - string[] result; - if (!userProvidedAliases.TryGetValue(propertyPath, out result)) - { - return defaultAliases; - } - else + if (userProvidedAliases != null && userProvidedAliases.TryGetValue(propertyPath, out var result)) { return result; } + + return defaultAliases; } - private string GetUserProvidedAlias(string propertyPath, string defaultAlias) + private static string GetUserProvidedAlias(IDictionary userProvidedAliases, string propertyPath, string defaultAlias) { - string[] columns; - if (!userProvidedAliases.TryGetValue(propertyPath, out columns)) - { - return defaultAlias; - } - else + if (userProvidedAliases != null && userProvidedAliases.TryGetValue(propertyPath, out var columns)) { return columns[0]; } + + return defaultAlias; } } } diff --git a/src/NHibernate/Loader/GeneratedCollectionAliases.cs b/src/NHibernate/Loader/GeneratedCollectionAliases.cs index 4dc00e229fe..932272ea19f 100644 --- a/src/NHibernate/Loader/GeneratedCollectionAliases.cs +++ b/src/NHibernate/Loader/GeneratedCollectionAliases.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using NHibernate.Persister.Collection; -using NHibernate.Util; namespace NHibernate.Loader { @@ -16,38 +15,39 @@ public class GeneratedCollectionAliases : ICollectionAliases private readonly string[] indexAliases; private readonly string[] elementAliases; private readonly string identifierAlias; - private readonly IDictionary userProvidedAliases; - public GeneratedCollectionAliases(IDictionary userProvidedAliases, ICollectionPersister persister, - string suffix) + public GeneratedCollectionAliases(IDictionary userProvidedAliases, ICollectionPersister persister, string suffix) { this.suffix = suffix; - this.userProvidedAliases = userProvidedAliases; - keyAliases = GetUserProvidedAliases(CollectionPersister.PropKey, persister.GetKeyColumnAliases(suffix)); - - indexAliases = GetUserProvidedAliases(CollectionPersister.PropIndex, persister.GetIndexColumnAliases(suffix)); + keyAliases = GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropKey, persister.GetKeyColumnAliases(suffix)); + indexAliases = GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropIndex, persister.GetIndexColumnAliases(suffix)); // NH-1612: Add aliases for all composite element properties to support access // to individual composite element properties in elements. elementAliases = persister.ElementType.IsComponentType - ? GetUserProvidedCompositeElementAliases(persister.GetElementColumnAliases(suffix)) - : GetUserProvidedAliases(CollectionPersister.PropElement, persister.GetElementColumnAliases(suffix)); + ? GetUserProvidedCompositeElementAliases(userProvidedAliases, persister.GetElementColumnAliases(suffix)) + : GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropElement, persister.GetElementColumnAliases(suffix)); - identifierAlias = GetUserProvidedAlias(CollectionPersister.PropId, persister.GetIdentifierColumnAlias(suffix)); + identifierAlias = GetUserProvidedAlias(userProvidedAliases, CollectionPersister.PropId, persister.GetIdentifierColumnAlias(suffix)); } public GeneratedCollectionAliases(ICollectionPersister persister, string str) - : this(CollectionHelper.EmptyDictionary(), persister, str) {} + : this(null, persister, str) {} - private string[] GetUserProvidedCompositeElementAliases(string[] defaultAliases) + private static string[] GetUserProvidedCompositeElementAliases(IDictionary userProvidedAliases, string[] defaultAliases) { var aliases = new List(); - foreach (KeyValuePair userProvidedAlias in userProvidedAliases) + if (userProvidedAliases != null) { - if (userProvidedAlias.Key.StartsWith(CollectionPersister.PropElement + ".", StringComparison.Ordinal)) + foreach (var userProvidedAlias in userProvidedAliases) { - aliases.AddRange(userProvidedAlias.Value); + if (userProvidedAlias.Key.StartsWith( + CollectionPersister.PropElement + ".", + StringComparison.Ordinal)) + { + aliases.AddRange(userProvidedAlias.Value); + } } } @@ -103,40 +103,31 @@ public override string ToString() base.ToString(), suffix, Join(keyAliases), Join(indexAliases), Join(elementAliases), identifierAlias); } - private static string Join(IEnumerable aliases) + private static string Join(string[] aliases) { - if (aliases == null) - { - return null; - } - - return string.Join(", ", aliases); + return aliases == null + ? null + : string.Join(", ", aliases); } - private string[] GetUserProvidedAliases(string propertyPath, string[] defaultAliases) + private static string[] GetUserProvidedAliases(IDictionary userProvidedAliases, string propertyPath, string[] defaultAliases) { - string[] result; - if (!userProvidedAliases.TryGetValue(propertyPath, out result)) - { - return defaultAliases; - } - else + if (userProvidedAliases != null && userProvidedAliases.TryGetValue(propertyPath, out var result)) { return result; } + + return defaultAliases; } - private string GetUserProvidedAlias(string propertyPath, string defaultAlias) + private static string GetUserProvidedAlias(IDictionary userProvidedAliases, string propertyPath, string defaultAlias) { - string[] columns; - if (!userProvidedAliases.TryGetValue(propertyPath, out columns)) - { - return defaultAlias; - } - else + if (userProvidedAliases != null && userProvidedAliases.TryGetValue(propertyPath, out var columns)) { return columns[0]; } + + return defaultAlias; } } } diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs index 86308f501d2..387b6d8fd17 100644 --- a/src/NHibernate/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Loader/Hql/QueryLoader.cs @@ -206,9 +206,9 @@ protected override ICollectionPersister[] CollectionPersisters public override IType[] CacheTypes => _cacheTypes; - protected override Dictionary[] CollectionUserProvidedAliases + protected override IDictionary GetCollectionUserProvidedAlias(int index) { - get { return _collectionUserProvidedAliases; } + return _collectionUserProvidedAliases?[index]; } private void Initialize(SelectClause selectClause) @@ -301,7 +301,7 @@ private void Initialize(SelectClause selectClause) // As bridge table will contain not-null values for filtered elements // So do alias substitution for collection persister with element persister // See test TestFilteredLinqQuery for details - CollectionUserProvidedAliases[collectionIndex] = new Dictionary + _collectionUserProvidedAliases[collectionIndex] = new Dictionary { {CollectionPersister.PropElement, _entityPersisters[i].GetIdentifierAliases(Suffixes[i])} };