diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3864/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3864/Fixture.cs
new file mode 100644
index 00000000000..14e612e9e89
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3864/Fixture.cs
@@ -0,0 +1,157 @@
+//------------------------------------------------------------------------------
+//
+// 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.Linq;
+using NUnit.Framework;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace NHibernate.Test.NHSpecificTest.NH3864
+{
+ using System.Threading.Tasks;
+ [TestFixture]
+ public class FixtureAsync : BugTestCase
+ {
+ protected override void OnSetUp()
+ {
+ Clear2ndLevelCache();
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ var p1 = new Person() { Name = "A" };
+ var p1c1 = new Person() { Name = "AA" };
+ var p1c2 = new Person() { Name = "AB" };
+ var p1c3 = new Person() { Name = "AC" };
+ p1.Children = new HashSet(new[] { p1c1, p1c2, p1c3 });
+ session.Save(p1);
+
+ var p2 = new Person() { Name = "B" };
+ var p2c1 = new Person() { Name = "BA" };
+ var p2c2 = new Person() { Name = "BB" };
+ var p2c3 = new Person() { Name = "BC" };
+ p2.Children = new HashSet(new[] { p2c1, p2c2, p2c3 });
+ session.Save(p2);
+
+ transaction.Commit();
+ }
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ session.Delete("from Person");
+ transaction.Commit();
+ }
+ }
+
+ [Test]
+ public async Task CacheableMulticriteria_QueryOverWithAliasedJoinQueryOverAsync()
+ {
+ foreach (var checkCache in new[] { false, true })
+ {
+ using (var sqlLogSpy = new SqlLogSpy())
+ {
+ using (var session = Sfi.OpenSession())
+ {
+ var query = CreateQueryOverWithAliasedJoinQueryOver(session);
+
+ var multiCriteria = session.CreateMultiCriteria();
+ multiCriteria.Add("myQuery", query);
+ multiCriteria.SetCacheable(true);
+
+ var list = (IList)await (multiCriteria.GetResultAsync("myQuery"));
+ AssertQueryResult(list);
+ }
+
+ if (checkCache && !string.IsNullOrEmpty(sqlLogSpy.GetWholeLog()))
+ {
+ Assert.Fail("SQL executed. 2nd level cache should be used instead.");
+ }
+ }
+ }
+ }
+
+ [Test]
+ public async Task CacheableMulticriteria_QueryOverWithJoinAliasAsync()
+ {
+ foreach (var checkCache in new[] { false, true })
+ {
+ using (var sqlLogSpy = new SqlLogSpy())
+ {
+ using (var s = Sfi.OpenSession())
+ {
+ var query = CreateQueryOverWithJoinAlias(s);
+
+ var multiCriteria = s.CreateMultiCriteria();
+ multiCriteria.Add("myQuery", query);
+ multiCriteria.SetCacheable(true);
+
+ var list = (IList)await (multiCriteria.GetResultAsync("myQuery"));
+ AssertQueryResult(list);
+ }
+ if (checkCache && !string.IsNullOrEmpty(sqlLogSpy.GetWholeLog()))
+ {
+ Assert.Fail("SQL executed. 2nd level cache should be used instead.");
+ }
+ }
+ }
+ }
+
+ private static void AssertQueryResult(IList list)
+ {
+ Assert.AreEqual(6, list.Count, "Returned records count is wrong.");
+ var person1 = list.FirstOrDefault(p => p.Name == "A");
+ Assert.NotNull(person1);
+ var person2 = list.FirstOrDefault(p => p.Name == "B");
+ Assert.NotNull(person2);
+
+ CollectionAssert.AreEquivalent(person1.Children.Select(c => c.Name), new[] { "AA", "AB", "AC" });
+ CollectionAssert.AreEquivalent(person2.Children.Select(c => c.Name), new[] { "BA", "BB", "BC" });
+ }
+
+ private static IFutureEnumerable CreateCacheableQueryWithSubquery(ISession session)
+ {
+ var subQuery = session.Query()
+ .WithOptions(p => p.SetCacheable(true));
+
+ var query = session.Query()
+ .FetchMany(p => p.Children)
+ .Where(p => subQuery.Contains(p) && p.Parent == null)
+ .WithOptions(o => o.SetCacheable(true))
+ .ToFuture();
+
+ return query;
+ }
+
+ private static IQueryOver CreateQueryOverWithJoinAlias(ISession session)
+ {
+ Person childAlias = null;
+ return session.QueryOver()
+ .Where(p => p.Parent == null)
+ .JoinAlias(x => x.Children, () => childAlias);
+ }
+
+ private static IQueryOver CreateQueryOverWithAliasedJoinQueryOver(ISession session)
+ {
+ Person childAlias = null;
+ return session.QueryOver()
+ .Where(p => p.Parent == null)
+ .JoinQueryOver(x => x.Children, () => childAlias);
+ }
+
+ private void Clear2ndLevelCache()
+ {
+ Sfi.EvictQueries();
+ Sfi.Evict(typeof(Person));
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/NH3864/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3864/Fixture.cs
new file mode 100644
index 00000000000..3c6076f7bc6
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/NH3864/Fixture.cs
@@ -0,0 +1,216 @@
+using NHibernate.Linq;
+using NUnit.Framework;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace NHibernate.Test.NHSpecificTest.NH3864
+{
+ [TestFixture]
+ public class Fixture : BugTestCase
+ {
+ protected override void OnSetUp()
+ {
+ Clear2ndLevelCache();
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ var p1 = new Person() { Name = "A" };
+ var p1c1 = new Person() { Name = "AA" };
+ var p1c2 = new Person() { Name = "AB" };
+ var p1c3 = new Person() { Name = "AC" };
+ p1.Children = new HashSet(new[] { p1c1, p1c2, p1c3 });
+ session.Save(p1);
+
+ var p2 = new Person() { Name = "B" };
+ var p2c1 = new Person() { Name = "BA" };
+ var p2c2 = new Person() { Name = "BB" };
+ var p2c3 = new Person() { Name = "BC" };
+ p2.Children = new HashSet(new[] { p2c1, p2c2, p2c3 });
+ session.Save(p2);
+
+ transaction.Commit();
+ }
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ session.Delete("from Person");
+ transaction.Commit();
+ }
+ }
+
+ [Test]
+ public void CacheableMulticriteria_QueryOverWithAliasedJoinQueryOver()
+ {
+ foreach (var checkCache in new[] { false, true })
+ {
+ using (var sqlLogSpy = new SqlLogSpy())
+ {
+ using (var session = Sfi.OpenSession())
+ {
+ var query = CreateQueryOverWithAliasedJoinQueryOver(session);
+
+ var multiCriteria = session.CreateMultiCriteria();
+ multiCriteria.Add("myQuery", query);
+ multiCriteria.SetCacheable(true);
+
+ var list = (IList)multiCriteria.GetResult("myQuery");
+ AssertQueryResult(list);
+ }
+
+ if (checkCache && !string.IsNullOrEmpty(sqlLogSpy.GetWholeLog()))
+ {
+ Assert.Fail("SQL executed. 2nd level cache should be used instead.");
+ }
+ }
+ }
+ }
+
+ [Test]
+ public void CacheableFuture_QueryOverWithAliasedJoinQueryOver()
+ {
+ foreach (var checkCache in new[] { false, true })
+ {
+ using (var sqlLogSpy = new SqlLogSpy())
+ {
+ using (var s = Sfi.OpenSession())
+ {
+ var query = CreateQueryOverWithAliasedJoinQueryOver(s)
+ .Cacheable()
+ .Future();
+
+ var list = query.ToList();
+ AssertQueryResult(list);
+ }
+ if (checkCache && !string.IsNullOrEmpty(sqlLogSpy.GetWholeLog()))
+ {
+ Assert.Fail("SQL executed. 2nd level cache should be used instead.");
+ }
+ }
+ }
+ }
+
+ [Test]
+ public void CacheableMulticriteria_QueryOverWithJoinAlias()
+ {
+ foreach (var checkCache in new[] { false, true })
+ {
+ using (var sqlLogSpy = new SqlLogSpy())
+ {
+ using (var s = Sfi.OpenSession())
+ {
+ var query = CreateQueryOverWithJoinAlias(s);
+
+ var multiCriteria = s.CreateMultiCriteria();
+ multiCriteria.Add("myQuery", query);
+ multiCriteria.SetCacheable(true);
+
+ var list = (IList)multiCriteria.GetResult("myQuery");
+ AssertQueryResult(list);
+ }
+ if (checkCache && !string.IsNullOrEmpty(sqlLogSpy.GetWholeLog()))
+ {
+ Assert.Fail("SQL executed. 2nd level cache should be used instead.");
+ }
+ }
+ }
+ }
+
+ [Test]
+ public void CacheableFuture_QueryOverWithJoinAlias()
+ {
+ foreach (var checkCache in new[] { false, true })
+ {
+ using (var sqlLogSpy = new SqlLogSpy())
+ {
+ using (var s = Sfi.OpenSession())
+ {
+ var query = CreateQueryOverWithJoinAlias(s)
+ .Cacheable()
+ .Future();
+
+ var list = query.ToList();
+ AssertQueryResult(list);
+ }
+ if (checkCache && !string.IsNullOrEmpty(sqlLogSpy.GetWholeLog()))
+ {
+ Assert.Fail("SQL executed. 2nd level cache should be used instead.");
+ }
+ }
+ }
+ }
+
+ [Test]
+ public void CacheableFuture_QueryWithSubQuery()
+ {
+ foreach (var checkCache in new[] { false, true })
+ {
+ using (var sqlLogSpy = new SqlLogSpy())
+ {
+ using (var session = Sfi.OpenSession())
+ {
+ var query = CreateCacheableQueryWithSubquery(session);
+
+ var list = query.ToList();
+ AssertQueryResult(list);
+ }
+ if (checkCache && !string.IsNullOrEmpty(sqlLogSpy.GetWholeLog()))
+ {
+ Assert.Fail("SQL executed. 2nd level cache should be used instead.");
+ }
+ }
+ }
+ }
+
+ private static void AssertQueryResult(IList list)
+ {
+ Assert.AreEqual(6, list.Count, "Returned records count is wrong.");
+ var person1 = list.FirstOrDefault(p => p.Name == "A");
+ Assert.NotNull(person1);
+ var person2 = list.FirstOrDefault(p => p.Name == "B");
+ Assert.NotNull(person2);
+
+ CollectionAssert.AreEquivalent(person1.Children.Select(c => c.Name), new[] { "AA", "AB", "AC" });
+ CollectionAssert.AreEquivalent(person2.Children.Select(c => c.Name), new[] { "BA", "BB", "BC" });
+ }
+
+ private static IFutureEnumerable CreateCacheableQueryWithSubquery(ISession session)
+ {
+ var subQuery = session.Query()
+ .WithOptions(p => p.SetCacheable(true));
+
+ var query = session.Query()
+ .FetchMany(p => p.Children)
+ .Where(p => subQuery.Contains(p) && p.Parent == null)
+ .WithOptions(o => o.SetCacheable(true))
+ .ToFuture();
+
+ return query;
+ }
+
+ private static IQueryOver CreateQueryOverWithJoinAlias(ISession session)
+ {
+ Person childAlias = null;
+ return session.QueryOver()
+ .Where(p => p.Parent == null)
+ .JoinAlias(x => x.Children, () => childAlias);
+ }
+
+ private static IQueryOver CreateQueryOverWithAliasedJoinQueryOver(ISession session)
+ {
+ Person childAlias = null;
+ return session.QueryOver()
+ .Where(p => p.Parent == null)
+ .JoinQueryOver(x => x.Children, () => childAlias);
+ }
+
+ private void Clear2ndLevelCache()
+ {
+ Sfi.EvictQueries();
+ Sfi.Evict(typeof(Person));
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/NH3864/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH3864/Mappings.hbm.xml
new file mode 100644
index 00000000000..13a053bcf8c
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/NH3864/Mappings.hbm.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/NHibernate.Test/NHSpecificTest/NH3864/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH3864/Person.cs
new file mode 100644
index 00000000000..7de6076091d
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/NH3864/Person.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+
+namespace NHibernate.Test.NHSpecificTest.NH3864
+{
+ public class Person
+ {
+ public virtual int Id { get; set; }
+
+ public virtual string Name { get; set; }
+
+ public virtual Person Parent { get; set; }
+
+ public virtual ISet Children { get; set; }
+ }
+}
\ No newline at end of file