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 new file mode 100644 index 00000000000..34b02db20e2 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// +// 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.Dialect; +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 NHibernate 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 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() + .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() + { + 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.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 new file mode 100644 index 00000000000..44f9719be0b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs @@ -0,0 +1,23 @@ +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 virtual ISet DocumentsFiltered { 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..aa0835d9a17 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs @@ -0,0 +1,106 @@ +using System.Linq; +using NHibernate.Dialect; +using NHibernate.Linq; +using NHibernate.Transform; +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(); + 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 NHibernate ordering the deletes, but this will cause + // loading the entities in the session. + + session.Delete("from System.Object"); + + transaction.Commit(); + } + } + + [Test] + public void TestUnfilteredLinqQuery() + { + using (var s = OpenSession()) + { + var query = s.Query() + .FetchMany(x => x.Documents) + .ToList(); + + Assert.That(query.Count, Is.EqualTo(1), "unfiltered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(2), "unfiltered asset documents"); + } + } + + [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() + .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() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + var query = s.Query() + .FetchMany(x => x.Documents) + .ToList(); + + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); + } + } + + [Test] + public void TestFilteredQueryOver() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + var query = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Documents) + .TransformUsing(Transformers.DistinctRootEntity) + .List(); + + 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.Test/NHSpecificTest/GH1994/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml new file mode 100644 index 00000000000..57a58acefa8 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..97c0781da4b 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; @@ -50,7 +51,7 @@ protected override void PostInstantiate() { bagCount++; } - collectionDescriptors[i] = new GeneratedCollectionAliases(collectionPersisters[i], collectionSuffixes[i]); + collectionDescriptors[i] = new GeneratedCollectionAliases(GetCollectionUserProvidedAlias(i), collectionPersisters[i], collectionSuffixes[i]); } } else @@ -66,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 71b127fb60e..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("key", persister.GetKeyColumnAliases(suffix)); - - indexAliases = GetUserProvidedAliases("index", 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("element", persister.GetElementColumnAliases(suffix)); + ? GetUserProvidedCompositeElementAliases(userProvidedAliases, persister.GetElementColumnAliases(suffix)) + : GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropElement, persister.GetElementColumnAliases(suffix)); - identifierAlias = GetUserProvidedAlias("id", 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("element.", 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 9bb71eb2d3b..387b6d8fd17 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 IDictionary GetCollectionUserProvidedAlias(int index) + { + return _collectionUserProvidedAliases?[index]; + } + 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++) { @@ -282,6 +290,24 @@ private void Initialize(SelectClause selectClause) _selectLength++; } + if (collectionFromElements != null && element.IsFetch && element.QueryableCollection?.IsManyToMany == true + && element.QueryableCollection.IsManyToManyFiltered(_queryTranslator.EnabledFilters)) + { + var collectionIndex = collectionFromElements.IndexOf(element); + + if (collectionIndex >= 0) + { + // 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])} + }; + } + } + _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 e03ad0fb9a0..0e7dc605d3b 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. /// @@ -1380,6 +1389,11 @@ public string GetManyToManyFilterFragment(string alias, IDictionary enabledFilters) + { + return IsManyToMany && (manyToManyWhereString != null || manyToManyFilterHelper.IsAffectedBy(enabledFilters)); + } public string[] ToColumns(string alias, string propertyName) { @@ -1512,17 +1526,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..88d2401ae2d 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,18 @@ public static int GetBatchSize(this ICollectionPersister persister) return 1; } + + /// + /// 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.IsManyToManyFiltered(enabledFilters); + } + + return persister.IsManyToMany && !string.IsNullOrEmpty(persister.GetManyToManyFilterFragment("x", enabledFilters)); + } } }