Skip to content

Commit b40c11f

Browse files
committed
Use entity aliases for collection persister to support many-to-many filtering
Fixes #1994
1 parent d166415 commit b40c11f

File tree

7 files changed

+159
-13
lines changed

7 files changed

+159
-13
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System.Linq;
12+
using NHibernate.Linq;
13+
using NHibernate.Transform;
14+
using NUnit.Framework;
15+
16+
namespace NHibernate.Test.NHSpecificTest.GH1994
17+
{
18+
using System.Threading.Tasks;
19+
[TestFixture]
20+
public class FixtureAsync : BugTestCase
21+
{
22+
protected override void OnSetUp()
23+
{
24+
using (var session = OpenSession())
25+
using (var transaction = session.BeginTransaction())
26+
{
27+
var a = new Asset();
28+
a.Documents.Add(new Document { IsDeleted = true });
29+
a.Documents.Add(new Document { IsDeleted = false });
30+
31+
session.Save(a);
32+
transaction.Commit();
33+
}
34+
}
35+
36+
protected override void OnTearDown()
37+
{
38+
using (var session = OpenSession())
39+
using (var transaction = session.BeginTransaction())
40+
{
41+
// The HQL delete does all the job inside the database without loading the entities, but it does
42+
// not handle delete order for avoiding violating constraints if any. Use
43+
// session.Delete("from System.Object");
44+
// instead if in need of having NHbernate ordering the deletes, but this will cause
45+
// loading the entities in the session.
46+
47+
session.Delete("from System.Object");
48+
49+
transaction.Commit();
50+
}
51+
}
52+
53+
[Test]
54+
public async Task TestUnfilteredLinqQueryAsync()
55+
{
56+
using (var s = OpenSession())
57+
{
58+
var query = await (s.Query<Asset>()
59+
.FetchMany(x => x.Documents)
60+
.ToListAsync());
61+
62+
Assert.That(query.Count, Is.EqualTo(1), "unfiltered assets");
63+
Assert.That(query[0].Documents.Count, Is.EqualTo(2), "unfiltered asset documents");
64+
}
65+
}
66+
67+
[Test]
68+
public async Task TestFilteredLinqQueryAsync()
69+
{
70+
using (var s = OpenSession())
71+
{
72+
s.EnableFilter("deletedFilter").SetParameter("deletedParam", false);
73+
var query = await (s.Query<Asset>()
74+
.FetchMany(x => x.Documents)
75+
.ToListAsync());
76+
77+
Assert.That(query.Count, Is.EqualTo(1), "filtered assets");
78+
Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents");
79+
}
80+
}
81+
82+
[Test]
83+
public async Task TestFilteredQueryOverAsync()
84+
{
85+
using (var s = OpenSession())
86+
{
87+
s.EnableFilter("deletedFilter").SetParameter("deletedParam", false);
88+
89+
var query = await (s.QueryOver<Asset>()
90+
.Fetch(SelectMode.Fetch, x => x.Documents)
91+
.TransformUsing(Transformers.DistinctRootEntity)
92+
.ListAsync<Asset>());
93+
94+
Assert.That(query.Count, Is.EqualTo(1), "filtered assets");
95+
Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents");
96+
}
97+
}
98+
}
99+
}

src/NHibernate/Async/Persister/Collection/AbstractCollectionPersister.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ namespace NHibernate.Persister.Collection
3939
{
4040
using System.Threading.Tasks;
4141
using System.Threading;
42+
4243
public abstract partial class AbstractCollectionPersister : ICollectionMetadata, ISqlLoadableCollection,
4344
IPostInsertIdentityPersister, ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister
4445
{

src/NHibernate/Loader/BasicLoader.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Generic;
12
using NHibernate.Engine;
23
using NHibernate.Persister.Collection;
34
using NHibernate.Persister.Entity;
@@ -27,7 +28,7 @@ protected override sealed ICollectionAliases[] CollectionAliases
2728

2829
protected abstract string[] Suffixes { get; }
2930
protected abstract string[] CollectionSuffixes { get; }
30-
31+
protected virtual Dictionary<string, string[]>[] CollectionUserProvidedAliases => null;
3132
protected override void PostInstantiate()
3233
{
3334
ILoadable[] persisters = EntityPersisters;
@@ -50,7 +51,7 @@ protected override void PostInstantiate()
5051
{
5152
bagCount++;
5253
}
53-
collectionDescriptors[i] = new GeneratedCollectionAliases(collectionPersisters[i], collectionSuffixes[i]);
54+
collectionDescriptors[i] = new GeneratedCollectionAliases(CollectionUserProvidedAliases?[i] ?? CollectionHelper.EmptyDictionary<string, string[]>(), collectionPersisters[i], collectionSuffixes[i]);
5455
}
5556
}
5657
else

src/NHibernate/Loader/GeneratedCollectionAliases.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@ public GeneratedCollectionAliases(IDictionary<string, string[]> userProvidedAlia
2424
this.suffix = suffix;
2525
this.userProvidedAliases = userProvidedAliases;
2626

27-
keyAliases = GetUserProvidedAliases("key", persister.GetKeyColumnAliases(suffix));
27+
keyAliases = GetUserProvidedAliases(CollectionPersister.PropKey, persister.GetKeyColumnAliases(suffix));
2828

29-
indexAliases = GetUserProvidedAliases("index", persister.GetIndexColumnAliases(suffix));
29+
indexAliases = GetUserProvidedAliases(CollectionPersister.PropIndex, persister.GetIndexColumnAliases(suffix));
3030

3131
// NH-1612: Add aliases for all composite element properties to support access
3232
// to individual composite element properties in <return-property> elements.
3333
elementAliases = persister.ElementType.IsComponentType
3434
? GetUserProvidedCompositeElementAliases(persister.GetElementColumnAliases(suffix))
35-
: GetUserProvidedAliases("element", persister.GetElementColumnAliases(suffix));
35+
: GetUserProvidedAliases(CollectionPersister.PropElement, persister.GetElementColumnAliases(suffix));
3636

37-
identifierAlias = GetUserProvidedAlias("id", persister.GetIdentifierColumnAlias(suffix));
37+
identifierAlias = GetUserProvidedAlias(CollectionPersister.PropId, persister.GetIdentifierColumnAlias(suffix));
3838
}
3939

4040
public GeneratedCollectionAliases(ICollectionPersister persister, string str)
@@ -45,7 +45,7 @@ private string[] GetUserProvidedCompositeElementAliases(string[] defaultAliases)
4545
var aliases = new List<string>();
4646
foreach (KeyValuePair<string, string[]> userProvidedAlias in userProvidedAliases)
4747
{
48-
if (userProvidedAlias.Key.StartsWith("element.", StringComparison.Ordinal))
48+
if (userProvidedAlias.Key.StartsWith(CollectionPersister.PropElement + ".", StringComparison.Ordinal))
4949
{
5050
aliases.AddRange(userProvidedAlias.Value);
5151
}

src/NHibernate/Loader/Hql/QueryLoader.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public partial class QueryLoader : BasicLoader
4646
private LockMode[] _defaultLockModes;
4747
private IType[] _cacheTypes;
4848
private ISet<ICollectionPersister> _uncacheableCollectionPersisters;
49+
private Dictionary<string, string[]>[] _collectionUserProvidedAliases;
4950

5051
public QueryLoader(QueryTranslatorImpl queryTranslator, ISessionFactoryImplementor factory, SelectClause selectClause)
5152
: base(factory)
@@ -205,6 +206,11 @@ protected override ICollectionPersister[] CollectionPersisters
205206

206207
public override IType[] CacheTypes => _cacheTypes;
207208

209+
protected override Dictionary<string, string[]>[] CollectionUserProvidedAliases
210+
{
211+
get { return _collectionUserProvidedAliases; }
212+
}
213+
208214
private void Initialize(SelectClause selectClause)
209215
{
210216
IList<FromElement> fromElementList = selectClause.FromElementsForLoad;
@@ -225,6 +231,8 @@ private void Initialize(SelectClause selectClause)
225231
_collectionOwners = new int[length];
226232
_collectionSuffixes = new string[length];
227233
CollectionFetches = new bool[length];
234+
if (collectionFromElements.Any(qc => qc.QueryableCollection.IsManyToMany))
235+
_collectionUserProvidedAliases = new Dictionary<string, string[]>[length];
228236

229237
for (int i = 0; i < length; i++)
230238
{
@@ -271,6 +279,7 @@ private void Initialize(SelectClause selectClause)
271279
// TODO should we just collect these like with the collections above?
272280
_sqlAliasSuffixes[i] = (size == 1) ? "" : i + "_";
273281
// sqlAliasSuffixes[i] = element.getColumnAliasSuffix();
282+
274283
_includeInSelect[i] = !element.IsFetch;
275284
EntityFetches[i] = element.IsFetch;
276285
if (element.IsFetch)
@@ -282,6 +291,20 @@ private void Initialize(SelectClause selectClause)
282291
_selectLength++;
283292
}
284293

294+
if (element.IsFetch && element.QueryableCollection?.IsManyToMany == true && element.QueryableCollection.IsAffectedByEnabledFilters(_queryTranslator.EnabledFilters))
295+
{
296+
var collectionIndex = collectionFromElements.IndexOf(element);
297+
298+
if (collectionIndex >= 0)
299+
{
300+
// See test TestFilteredLinqQuery to see why this replacement is needed
301+
CollectionUserProvidedAliases[collectionIndex] = new Dictionary<string, string[]>
302+
{
303+
{CollectionPersister.PropElement, _entityPersisters[i].GetIdentifierAliases(Suffixes[i]) }
304+
};
305+
}
306+
}
307+
285308
_owners[i] = -1; //by default
286309
if (element.IsFetch)
287310
{

src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@
2727

2828
namespace NHibernate.Persister.Collection
2929
{
30+
internal static class CollectionPersister
31+
{
32+
/// <summary> The property name of the "special" identifier property</summary>
33+
public const string PropId = "id";
34+
public const string PropElement = "element";
35+
public const string PropKey = "key";
36+
public const string PropIndex = "index";
37+
}
38+
3039
/// <summary>
3140
/// Summary description for AbstractCollectionPersister.
3241
/// </summary>
@@ -1487,10 +1496,14 @@ public override string ToString()
14871496
}
14881497

14891498
public bool IsAffectedByEnabledFilters(ISessionImplementor session)
1499+
{
1500+
return IsAffectedByEnabledFilters(session.EnabledFilters);
1501+
}
1502+
public bool IsAffectedByEnabledFilters(IDictionary<string, IFilter> enabledFilters)
14901503
{
14911504
return
1492-
filterHelper.IsAffectedBy(session.EnabledFilters)
1493-
|| (IsManyToMany && manyToManyFilterHelper.IsAffectedBy(session.EnabledFilters));
1505+
filterHelper.IsAffectedBy(enabledFilters)
1506+
|| (IsManyToMany && manyToManyFilterHelper.IsAffectedBy(enabledFilters));
14941507
}
14951508

14961509
public string[] GetCollectionPropertyColumnAliases(string propertyName, string suffix)
@@ -1512,17 +1525,17 @@ public string[] GetCollectionPropertyColumnAliases(string propertyName, string s
15121525

15131526
public void InitCollectionPropertyMap()
15141527
{
1515-
InitCollectionPropertyMap("key", keyType, keyColumnAliases, keyColumnNames);
1516-
InitCollectionPropertyMap("element", elementType, elementColumnAliases, elementColumnNames);
1528+
InitCollectionPropertyMap(CollectionPersister.PropKey, keyType, keyColumnAliases, keyColumnNames);
1529+
InitCollectionPropertyMap(CollectionPersister.PropElement, elementType, elementColumnAliases, elementColumnNames);
15171530

15181531
if (hasIndex)
15191532
{
1520-
InitCollectionPropertyMap("index", indexType, indexColumnAliases, indexColumnNames);
1533+
InitCollectionPropertyMap(CollectionPersister.PropIndex, indexType, indexColumnAliases, indexColumnNames);
15211534
}
15221535

15231536
if (hasIdentifier)
15241537
{
1525-
InitCollectionPropertyMap("id", identifierType, new string[] {identifierColumnAlias},
1538+
InitCollectionPropertyMap(CollectionPersister.PropId, identifierType, new string[] {identifierColumnAlias},
15261539
new string[] {identifierColumnName});
15271540
}
15281541
}

src/NHibernate/Persister/Collection/ICollectionPersister.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ public partial interface ICollectionPersister
285285
object NotFoundObject { get; }
286286
}
287287

288+
//6.0 TODO: Merge into ICollectionPersister
288289
public static class CollectionPersisterExtensions
289290
{
290291
/// <summary>
@@ -304,5 +305,13 @@ public static int GetBatchSize(this ICollectionPersister persister)
304305

305306
return 1;
306307
}
308+
internal static bool IsAffectedByEnabledFilters(this ICollectionPersister persister, IDictionary<string, IFilter> enabledFilters)
309+
{
310+
if (persister is AbstractCollectionPersister acp)
311+
{
312+
return acp.IsAffectedByEnabledFilters(enabledFilters);
313+
}
314+
return false;
315+
}
307316
}
308317
}

0 commit comments

Comments
 (0)