diff --git a/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs
new file mode 100644
index 00000000000..1b3a894bc4e
--- /dev/null
+++ b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs
@@ -0,0 +1,463 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Criterion;
+using NHibernate.Dialect;
+using NHibernate.Mapping.ByCode;
+using NHibernate.SqlCommand;
+using NHibernate.Transform;
+using NUnit.Framework;
+
+namespace NHibernate.Test.Criteria
+{
+ using System.Threading.Tasks;
+ ///
+ /// GH948
+ ///
+ [TestFixture]
+ public class EntityProjectionsTestAsync : TestCaseMappingByCode
+ {
+ private EntityWithCompositeId _entityWithCompositeId;
+
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+ mapper.Class(
+ rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+
+ rc.Version(ep => ep.Version, vm => { });
+
+ rc.Property(x => x.Name);
+
+ rc.Property(ep => ep.LazyProp, m => m.Lazy(true));
+
+ rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id"));
+ rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id"));
+ rc.ManyToOne(ep => ep.SameTypeChild, m => m.Column("SameTypeChildId"));
+
+ rc.Bag(
+ ep => ep.ChildrenList,
+ m =>
+ {
+ m.Cascade(Mapping.ByCode.Cascade.All);
+ m.Inverse(true);
+ },
+ a => a.OneToMany());
+
+ });
+
+ mapper.Class(
+ rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+ rc.Property(x => x.Name);
+ });
+
+ mapper.Class(
+ rc =>
+ {
+ rc.ComponentAsId(
+ e => e.Key,
+ ekm =>
+ {
+ ekm.Property(ek => ek.Id1);
+ ekm.Property(ek => ek.Id2);
+ });
+
+ rc.Property(e => e.Name);
+ });
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+
+ protected override void OnTearDown()
+ {
+ using (ISession session = OpenSession())
+ using (ITransaction transaction = session.BeginTransaction())
+ {
+ session.Delete("from System.Object");
+
+ session.Flush();
+ transaction.Commit();
+ }
+ }
+
+ protected override void OnSetUp()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ var child1 = new EntitySimpleChild
+ {
+ Name = "Child1"
+ };
+ var child2 = new EntitySimpleChild
+ {
+ Name = "Child1"
+ };
+
+ var parent = new EntityComplex
+ {
+ Name = "ComplexEnityParent",
+ Child1 = child1,
+ Child2 = child2,
+ LazyProp = "SomeBigValue",
+ SameTypeChild = new EntityComplex()
+ {
+ Name = "ComplexEntityChild"
+ }
+ };
+
+ _entityWithCompositeId = new EntityWithCompositeId
+ {
+ Key = new CompositeKey
+ {
+ Id1 = 1,
+ Id2 = 2
+ },
+ Name = "Composite"
+ };
+
+ session.Save(child1);
+ session.Save(child2);
+ session.Save(parent.SameTypeChild);
+ session.Save(parent);
+ session.Save(_entityWithCompositeId);
+
+ session.Flush();
+ transaction.Commit();
+ }
+ }
+
+ [Test]
+ public async Task RootEntityProjectionFullyInitializedAndWithUnfetchedLazyPropertiesByDefaultAsync()
+ {
+ using (var sqlLog = new SqlLogSpy())
+ using (var session = OpenSession())
+ {
+ Sfi.Statistics.Clear();
+ EntityComplex entityRoot = await (session
+ .QueryOver()
+ .Where(ec => ec.LazyProp != null)
+ .Select(Projections.RootEntity())
+ .Take(1).SingleOrDefaultAsync());
+
+ Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized by default");
+ Assert.That(session.IsReadOnly(entityRoot), Is.False, "Object must not be readonly by default");
+ Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.False, "Lazy properties should not be initialized by default.");
+ Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
+ }
+ }
+
+ [Test]
+ public async Task RootEntityProjectionLazyAsync()
+ {
+ using (var session = OpenSession())
+ {
+ EntityComplex entityRoot = await (session
+ .QueryOver()
+ .Select(Projections.RootEntity().SetLazy(true))
+ .Take(1).SingleOrDefaultAsync());
+
+
+ Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.False, "Object must be lazy loaded.");
+ }
+ }
+
+ [Test]
+ public async Task AliasedEntityProjectionAsync()
+ {
+ using (var sqlLog = new SqlLogSpy())
+ using (var session = OpenSession())
+ {
+ EntitySimpleChild child1 = null;
+ child1 = await (session
+ .QueryOver()
+ .JoinAlias(ep => ep.Child1, () => child1)
+ .Select(Projections.Entity(() => child1))
+ .Take(1).SingleOrDefaultAsync());
+
+ Assert.That(child1, Is.Not.Null);
+ Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized");
+ Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
+ }
+ }
+
+ [Test]
+ public async Task EntityProjectionAsSelectExpressionAsync()
+ {
+ using (var sqlLog = new SqlLogSpy())
+ using (var session = OpenSession())
+ {
+ EntitySimpleChild child1 = null;
+ child1 = await (session
+ .QueryOver()
+ .JoinAlias(ep => ep.Child1, () => child1)
+ .Select(ec => child1.AsEntity())
+ .Take(1).SingleOrDefaultAsync());
+
+ Assert.That(child1, Is.Not.Null);
+ Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized");
+ Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
+ }
+ }
+
+ [Test]
+ public async Task EntityProjectionLockModeAsync()
+ {
+ if (Dialect is Oracle8iDialect)
+ Assert.Ignore("Oracle is not supported due to #1352 bug (NH-3902)");
+
+ var upgradeHint = Dialect.ForUpdateString;
+ if(string.IsNullOrEmpty(upgradeHint))
+ upgradeHint = this.Dialect.AppendLockHint(LockMode.Upgrade, string.Empty);
+ if (string.IsNullOrEmpty(upgradeHint))
+ {
+ Assert.Ignore($"Upgrade hint is not supported by dialect {Dialect.GetType().Name}");
+ }
+
+ using (var sqlLog = new SqlLogSpy())
+ using (var session = OpenSession())
+ {
+ EntitySimpleChild child1 = null;
+ child1 = await (session
+ .QueryOver()
+ .JoinAlias(ep => ep.Child1, () => child1)
+ .Lock(() => child1).Upgrade
+ .Select(Projections.Entity(() => child1))
+ .Take(1).SingleOrDefaultAsync());
+
+ Assert.That(child1, Is.Not.Null);
+ Assert.That(NHibernateUtil.IsInitialized(child1), Is.True, "Object must be initialized");
+ Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
+ Assert.That(sqlLog.Appender.GetEvents()[0].RenderedMessage, Does.Contain(upgradeHint));
+ }
+ }
+
+ [Test]
+ public async Task MultipleLazyEntityProjectionsAsync()
+ {
+ using (var session = OpenSession())
+ {
+ EntitySimpleChild child1 = null;
+ EntityComplex root = null;
+ EntityComplex sameTypeChild = null;
+ EntitySimpleChild child2 = null;
+
+ var result = await (session
+ .QueryOver(() => root)
+ .JoinAlias(ep => ep.SameTypeChild, () => sameTypeChild)
+ .JoinAlias(ep => ep.Child1, () => child1)
+ .JoinAlias(ep => ep.Child2, () => child2)
+ .Select(
+ Projections.RootEntity().SetLazy(true),
+ Projections.Entity(() => child1).SetLazy(true),
+ Projections.Entity(() => sameTypeChild).SetLazy(true),
+ Projections.Entity(() => child2).SetLazy(true)
+ )
+ .Take(1).SingleOrDefaultAsync