diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs new file mode 100644 index 00000000000..07b1a219f83 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs @@ -0,0 +1,174 @@ +//------------------------------------------------------------------------------ +// +// 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.Cfg.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3352 +{ + using System.Threading.Tasks; + [TestFixture] + public class FetchFromNotMappedBaseClassFixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name, m => m.Lazy(true)); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.ManyToOne(x => x.Parent, m => m.ForeignKey("none")); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Component(x => x.Component); + }); + mapper.Component(rc => + { + rc.Property(x => x.Field); + rc.ManyToOne(x => x.Entity, m => m.ForeignKey("none")); + rc.Lazy(true); + }); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var np = new EntityComponentMapped { Component = new Component { Field = "x" } }; + session.Save(np); + var e = new EntityParentMapped { Parent = np }; + session.Save(e); + var nameMapped = new EntityNameMapped { Name = "lazy" }; + session.Save(nameMapped); + np.Component.Entity = nameMapped; + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public async Task CanFetchLazyComponentFromNotMappedBaseClassAsync() + { + using var session = OpenSession(); + var list = await (session.Query().Fetch(x => x.Component).ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + } + + [Test] + public async Task CanFetchLazyComponentThenEntityFromNotMappedBaseClassAsync() + { + using var session = OpenSession(); + var list = await (session.Query() + .Fetch(x => x.Component) + .ThenFetch(x => x.Entity) + .ThenFetch(x => x.Name) + .ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + Assert.That(result.Component.Entity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.Component.Entity), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result.Component.Entity, nameof(result.Name)), Is.True); + Assert.That(result.Component.Entity.Name, Is.EqualTo("lazy")); + } + + [Test] + public async Task CanFetchLazyPropertyFromNotMappedBaseClassAsync() + { + using var session = OpenSession(); + var list = await (session.Query().Fetch(x => x.Name).ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Name))); + Assert.That(result.Name, Is.EqualTo("lazy")); + } + + [Test] + public async Task CanThenFetchLazyComponentFromNotMappedBaseClassAsync() + { + using var session = OpenSession(); + var list = await (session.Query().Fetch(x => x.Parent).ThenFetch(x => x.Component).ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0].Parent; + Assert.That(NHibernateUtil.IsInitialized(result), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + } + + [KnownBug("GH-3356")] + [Test(Description = "GH-3356" )] + public async Task FetchAfterSelectAsync() + { + using var log = new SqlLogSpy(); + + using var s = OpenSession(); + var list = await (s.Query() + .Select(x => x.Parent) + .Fetch(x => x.Component) + .ThenFetch(x => x.Entity) + .ThenFetch(x => x.Name) + .ToListAsync()); + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + Assert.That(result.Component.Entity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.Component.Entity), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result.Component.Entity, nameof(result.Name)), Is.True); + Assert.That(result.Component.Entity.Name, Is.EqualTo("lazy")); + } + + [Test] + public async Task CanFetchEntityFromNotMappedBaseClassAsync() + { + using var session = OpenSession(); + var list = await (session.Query().Fetch(x => x.Parent).ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(1)); + Assert.That(list[0].Parent, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(list[0].Parent)); + } + + [Test] + public void FetchNotMappedAssociationThrowsAsync() + { + using var session = OpenSession(); + var query = session.Query().Fetch(x => x.Parent); + + Assert.ThrowsAsync(() => query.ToListAsync()); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3352/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3352/Entity.cs new file mode 100644 index 00000000000..dc7800cc977 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3352/Entity.cs @@ -0,0 +1,31 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH3352 +{ + public class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual EntityComponentMapped Parent { get; set; } + public virtual Component Component { get; set; } + } + + public class EntityNameMapped : Entity + { + } + + public class EntityParentMapped : Entity + { + } + + public class EntityComponentMapped : Entity + { + } + + public class Component + { + public string Field { get; set; } + + public EntityNameMapped Entity { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs new file mode 100644 index 00000000000..5b66027c963 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs @@ -0,0 +1,163 @@ +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3352 +{ + [TestFixture] + public class FetchFromNotMappedBaseClassFixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name, m => m.Lazy(true)); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.ManyToOne(x => x.Parent, m => m.ForeignKey("none")); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Component(x => x.Component); + }); + mapper.Component(rc => + { + rc.Property(x => x.Field); + rc.ManyToOne(x => x.Entity, m => m.ForeignKey("none")); + rc.Lazy(true); + }); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var np = new EntityComponentMapped { Component = new Component { Field = "x" } }; + session.Save(np); + var e = new EntityParentMapped { Parent = np }; + session.Save(e); + var nameMapped = new EntityNameMapped { Name = "lazy" }; + session.Save(nameMapped); + np.Component.Entity = nameMapped; + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public void CanFetchLazyComponentFromNotMappedBaseClass() + { + using var session = OpenSession(); + var list = session.Query().Fetch(x => x.Component).ToList(); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + } + + [Test] + public void CanFetchLazyComponentThenEntityFromNotMappedBaseClass() + { + using var session = OpenSession(); + var list = session.Query() + .Fetch(x => x.Component) + .ThenFetch(x => x.Entity) + .ThenFetch(x => x.Name) + .ToList(); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + Assert.That(result.Component.Entity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.Component.Entity), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result.Component.Entity, nameof(result.Name)), Is.True); + Assert.That(result.Component.Entity.Name, Is.EqualTo("lazy")); + } + + [Test] + public void CanFetchLazyPropertyFromNotMappedBaseClass() + { + using var session = OpenSession(); + var list = session.Query().Fetch(x => x.Name).ToList(); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Name))); + Assert.That(result.Name, Is.EqualTo("lazy")); + } + + [Test] + public void CanThenFetchLazyComponentFromNotMappedBaseClass() + { + using var session = OpenSession(); + var list = session.Query().Fetch(x => x.Parent).ThenFetch(x => x.Component).ToList(); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0].Parent; + Assert.That(NHibernateUtil.IsInitialized(result), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + } + + [KnownBug("GH-3356")] + [Test(Description = "GH-3356" )] + public void FetchAfterSelect() + { + using var log = new SqlLogSpy(); + + using var s = OpenSession(); + var list = s.Query() + .Select(x => x.Parent) + .Fetch(x => x.Component) + .ThenFetch(x => x.Entity) + .ThenFetch(x => x.Name) + .ToList(); + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + Assert.That(result.Component.Entity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.Component.Entity), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result.Component.Entity, nameof(result.Name)), Is.True); + Assert.That(result.Component.Entity.Name, Is.EqualTo("lazy")); + } + + [Test] + public void CanFetchEntityFromNotMappedBaseClass() + { + using var session = OpenSession(); + var list = session.Query().Fetch(x => x.Parent).ToList(); + + Assert.That(list, Has.Count.EqualTo(1)); + Assert.That(list[0].Parent, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(list[0].Parent)); + } + + [Test] + public void FetchNotMappedAssociationThrows() + { + using var session = OpenSession(); + var query = session.Query().Fetch(x => x.Parent); + + Assert.Throws(() => query.ToList()); + } + } +} diff --git a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs index 31401f7df81..7c18f9476f4 100644 --- a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs +++ b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using NHibernate.Hql.Ast; +using NHibernate.Persister.Entity; using NHibernate.Type; using Remotion.Linq.EagerFetching; @@ -54,15 +55,18 @@ private void Process( .GetClassMetadata(resultOperator.RelationMember.ReflectedType); if (metadata == null) { - var entityName = queryModelVisitor.VisitorParameters.SessionFactory.GetImplementors( - resultOperator.RelationMember.ReflectedType.FullName).FirstOrDefault(); - if (!string.IsNullOrEmpty(entityName)) + foreach (var entityName in queryModelVisitor.VisitorParameters.SessionFactory + .GetImplementors(resultOperator.RelationMember.ReflectedType.FullName)) { - metadata = queryModelVisitor.VisitorParameters.SessionFactory.GetClassMetadata(entityName); + if (queryModelVisitor.VisitorParameters.SessionFactory.GetClassMetadata(entityName) is IPropertyMapping propertyMapping + && propertyMapping.TryToType(resultOperator.RelationMember.Name, out propType)) + break; } } - - propType = metadata?.GetPropertyType(resultOperator.RelationMember.Name); + else + { + propType = metadata.GetPropertyType(resultOperator.RelationMember.Name); + } } if (propType != null && !propType.IsAssociationType)