From 7dc2bc8b4c2851f71df02681df7567d734383f66 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 2 Aug 2021 12:35:03 +0300 Subject: [PATCH 1/5] Fix casting to unmapped interface in LINQ --- .../NHSpecificTest/GH2858/FixtureByCode.cs | 216 ++++++++++++++++++ .../NHSpecificTest/GH2858/Entity.cs | 61 +++++ .../NHSpecificTest/GH2858/FixtureByCode.cs | 204 +++++++++++++++++ src/NHibernate/Util/ExpressionsHelper.cs | 12 + 4 files changed, 493 insertions(+) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH2858/FixtureByCode.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2858/Entity.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2858/FixtureByCode.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2858/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2858/FixtureByCode.cs new file mode 100644 index 00000000000..d02ce91f7ee --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2858/FixtureByCode.cs @@ -0,0 +1,216 @@ +//------------------------------------------------------------------------------ +// +// 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; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Hql.Ast; +using NHibernate.Linq.Functions; +using NHibernate.Linq.Visitors; +using NHibernate.Mapping.ByCode; +using NHibernate.Type; +using NHibernate.Util; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2858 +{ + using System.Threading.Tasks; + /// + /// Fixture using 'by code' mappings + /// + /// + /// This fixture is identical to except the mapping is performed + /// by code in the GetMappings method, and does not require the Mappings.hbm.xml file. Use this approach + /// if you prefer. + /// + [TestFixture] + public class ByCodeFixtureAsync : 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); + }); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Department, m => m.Column("DepartmentId")); + }); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.IdBag( + x => x.Departments, + m => m.Table("IssuesToDepartments"), + r => r.ManyToMany()); + rc.ManyToOne(x => x.Project, m => m.Column("ProjectId")); + }); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.ManyToOne(x => x.Issue, m => m.Column("IssueId")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var deptA = new Department {Name = "A"}; + session.Save(deptA); + var deptB = new Department {Name = "B"}; + session.Save(deptB); + var deptC = new Department {Name = "C"}; + session.Save(deptC); + var deptD = new Department {Name = "D"}; + session.Save(deptD); + var deptE = new Department {Name = "E"}; + session.Save(deptE); + + var projectX = new Project {Name = "X", Department = deptA}; + session.Save(projectX); + var projectY = new Project {Name = "Y", Department = deptC}; + session.Save(projectY); + var projectZ = new Project {Name = "Z", Department = deptE}; + session.Save(projectZ); + + + var issue1 = new Issue {Name = "TEST-1", Project = projectX,}; + session.Save(issue1); + var issue2 = new Issue {Name = "TEST-2", Project = projectX, Departments = {deptA},}; + session.Save(issue2); + var issue3 = new Issue {Name = "TEST-3", Project = projectX, Departments = {deptA, deptB},}; + session.Save(issue3); + var issue4 = new Issue {Name = "TEST-4", Project = projectY,}; + session.Save(issue4); + var issue5 = new Issue {Name = "TEST-5", Project = projectY, Departments = {deptD}}; + session.Save(issue5); + + session.Save(new TimeChunk {Issue = issue1}); + session.Save(new TimeChunk {Issue = issue1}); + session.Save(new TimeChunk {Issue = issue2}); + session.Save(new TimeChunk {Issue = issue2}); + session.Save(new TimeChunk {Issue = issue3}); + session.Save(new TimeChunk {Issue = issue3}); + session.Save(new TimeChunk {Issue = issue4}); + session.Save(new TimeChunk {Issue = issue4}); + session.Save(new TimeChunk {Issue = issue5}); + session.Save(new TimeChunk {Issue = issue5}); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + transaction.Commit(); + } + } + + [KnownBug("GH-2857")] + [Test] + public async Task GroupLevelQueryAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var query = session.Query() + .Select(x => new object[] {(object) x}) + .GroupBy(g => new object[] {(Guid?) (((ITimeChunk) g[0]).Issue.Project.Id)}, v => (ITimeChunk) v[0]) + .Select(r => new object[] {r.Key, r.Sum(t => (int?) t.Seconds)}); + + var results = await (query.ToListAsync()); + Assert.That(results, Has.Count.EqualTo(2)); + + await (transaction.RollbackAsync()); + } + } + + [KnownBug("GH-2857")] + [Test] + public async Task GroupLevelQuery_SimplifiedAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var query = session.Query() + .Select(x => new object[] {x}) + .GroupBy(g => new object[] {((ITimeChunk) g[0]).Issue.Project.Id}, v => (ITimeChunk) v[0]) + .Select(r => new object[] {r.Key, r.Sum(t => (int?) t.Seconds)}); + + var results = await (query.ToListAsync()); + Assert.That(results, Has.Count.EqualTo(2)); + + await (transaction.RollbackAsync()); + } + } + + [Test] + public async Task SelectManySubQueryWithCoalesceAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var usedDepartments = session.Query() + .SelectMany(x => ((IEnumerable) x.Issue.Departments).DefaultIfEmpty().Select(d => (object) ((Guid?) ((Guid?) (((IDepartment) d).Id) ?? x.Issue.Project.Department.Id)))) + .Where(id => id != null) + .Select(id => (Guid?) id); + + var result = session.Query() + .Where(d => usedDepartments.Contains(d.Id)) + .Select(d => new {d.Id, d.Name}); + + Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(4)); + await (transaction.RollbackAsync()); + } + } + + [Test] + public async Task SelectManySubQueryWithCoalesce_SimplifiedAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var usedDepartments = session.Query() + .SelectMany(x => ((IEnumerable) x.Issue.Departments).DefaultIfEmpty().Select(d => (Guid?) ((IDepartment) d).Id ?? x.Issue.Project.Department.Id)); + + var result = session.Query() + .Where(d => usedDepartments.Contains(d.Id)) + .Select(d => new {d.Id, d.Name}); + + Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(4)); + await (transaction.RollbackAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2858/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH2858/Entity.cs new file mode 100644 index 00000000000..108d0a38aae --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2858/Entity.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH2858 +{ + public interface IDepartment + { + Guid Id { get; set; } + string Name { get; set; } + } + + public class Department : IDepartment + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } + + public interface IProject + { + Guid Id { get; set; } + string Name { get; set; } + Department Department { get; set; } + } + + public class Project : IProject + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual Department Department { get; set; } + } + + public interface IIssue + { + Guid Id { get; set; } + string Name { get; set; } + Project Project { get; set; } + IList Departments { get; set; } + } + + public class Issue : IIssue + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual IList Departments { get; set; } = new List(); + public virtual Project Project { get; set; } + } + + public interface ITimeChunk + { + Guid Id { get; set; } + Issue Issue { get; set; } + int Seconds { get; set; } + } + + public class TimeChunk : ITimeChunk + { + public virtual Guid Id { get; set; } + public virtual Issue Issue { get; set; } + public virtual int Seconds { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2858/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH2858/FixtureByCode.cs new file mode 100644 index 00000000000..48cb4fdc976 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2858/FixtureByCode.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Hql.Ast; +using NHibernate.Linq.Functions; +using NHibernate.Linq.Visitors; +using NHibernate.Mapping.ByCode; +using NHibernate.Type; +using NHibernate.Util; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2858 +{ + /// + /// Fixture using 'by code' mappings + /// + /// + /// This fixture is identical to except the mapping is performed + /// by code in the GetMappings method, and does not require the Mappings.hbm.xml file. Use this approach + /// if you prefer. + /// + [TestFixture] + public class ByCodeFixture : 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); + }); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Department, m => m.Column("DepartmentId")); + }); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.IdBag( + x => x.Departments, + m => m.Table("IssuesToDepartments"), + r => r.ManyToMany()); + rc.ManyToOne(x => x.Project, m => m.Column("ProjectId")); + }); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.ManyToOne(x => x.Issue, m => m.Column("IssueId")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var deptA = new Department {Name = "A"}; + session.Save(deptA); + var deptB = new Department {Name = "B"}; + session.Save(deptB); + var deptC = new Department {Name = "C"}; + session.Save(deptC); + var deptD = new Department {Name = "D"}; + session.Save(deptD); + var deptE = new Department {Name = "E"}; + session.Save(deptE); + + var projectX = new Project {Name = "X", Department = deptA}; + session.Save(projectX); + var projectY = new Project {Name = "Y", Department = deptC}; + session.Save(projectY); + var projectZ = new Project {Name = "Z", Department = deptE}; + session.Save(projectZ); + + + var issue1 = new Issue {Name = "TEST-1", Project = projectX,}; + session.Save(issue1); + var issue2 = new Issue {Name = "TEST-2", Project = projectX, Departments = {deptA},}; + session.Save(issue2); + var issue3 = new Issue {Name = "TEST-3", Project = projectX, Departments = {deptA, deptB},}; + session.Save(issue3); + var issue4 = new Issue {Name = "TEST-4", Project = projectY,}; + session.Save(issue4); + var issue5 = new Issue {Name = "TEST-5", Project = projectY, Departments = {deptD}}; + session.Save(issue5); + + session.Save(new TimeChunk {Issue = issue1}); + session.Save(new TimeChunk {Issue = issue1}); + session.Save(new TimeChunk {Issue = issue2}); + session.Save(new TimeChunk {Issue = issue2}); + session.Save(new TimeChunk {Issue = issue3}); + session.Save(new TimeChunk {Issue = issue3}); + session.Save(new TimeChunk {Issue = issue4}); + session.Save(new TimeChunk {Issue = issue4}); + session.Save(new TimeChunk {Issue = issue5}); + session.Save(new TimeChunk {Issue = issue5}); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + transaction.Commit(); + } + } + + [KnownBug("GH-2857")] + [Test] + public void GroupLevelQuery() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var query = session.Query() + .Select(x => new object[] {(object) x}) + .GroupBy(g => new object[] {(Guid?) (((ITimeChunk) g[0]).Issue.Project.Id)}, v => (ITimeChunk) v[0]) + .Select(r => new object[] {r.Key, r.Sum(t => (int?) t.Seconds)}); + + var results = query.ToList(); + Assert.That(results, Has.Count.EqualTo(2)); + + transaction.Rollback(); + } + } + + [KnownBug("GH-2857")] + [Test] + public void GroupLevelQuery_Simplified() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var query = session.Query() + .Select(x => new object[] {x}) + .GroupBy(g => new object[] {((ITimeChunk) g[0]).Issue.Project.Id}, v => (ITimeChunk) v[0]) + .Select(r => new object[] {r.Key, r.Sum(t => (int?) t.Seconds)}); + + var results = query.ToList(); + Assert.That(results, Has.Count.EqualTo(2)); + + transaction.Rollback(); + } + } + + [Test] + public void SelectManySubQueryWithCoalesce() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var usedDepartments = session.Query() + .SelectMany(x => ((IEnumerable) x.Issue.Departments).DefaultIfEmpty().Select(d => (object) ((Guid?) ((Guid?) (((IDepartment) d).Id) ?? x.Issue.Project.Department.Id)))) + .Where(id => id != null) + .Select(id => (Guid?) id); + + var result = session.Query() + .Where(d => usedDepartments.Contains(d.Id)) + .Select(d => new {d.Id, d.Name}); + + Assert.That(result.ToList(), Has.Count.EqualTo(4)); + transaction.Rollback(); + } + } + + [Test] + public void SelectManySubQueryWithCoalesce_Simplified() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var usedDepartments = session.Query() + .SelectMany(x => ((IEnumerable) x.Issue.Departments).DefaultIfEmpty().Select(d => (Guid?) ((IDepartment) d).Id ?? x.Issue.Project.Department.Id)); + + var result = session.Query() + .Where(d => usedDepartments.Contains(d.Id)) + .Select(d => new {d.Id, d.Name}); + + Assert.That(result.ToList(), Has.Count.EqualTo(4)); + transaction.Rollback(); + } + } + } +} diff --git a/src/NHibernate/Util/ExpressionsHelper.cs b/src/NHibernate/Util/ExpressionsHelper.cs index 221b8031266..6e3ba896e08 100644 --- a/src/NHibernate/Util/ExpressionsHelper.cs +++ b/src/NHibernate/Util/ExpressionsHelper.cs @@ -526,6 +526,18 @@ private static bool TryGetEntityPersister( return persister != null; } + if (convertedType.IsAssignableFrom(currentEntityPersister.MappedClass)) + { + // If it's casting to interface or base class of the same entity + var results = sessionFactory.GetImplementors(convertedType.FullName); + if (results.Length == 1) + { + persister = sessionFactory.TryGetEntityPersister(results[0]); + if (persister != null) + return true; + } + } + return TryGetEntityPersister(convertedType, sessionFactory, out persister); } From 91637052db69405c4db8e8da2a7bc28c29510774 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 2 Aug 2021 13:33:31 +0300 Subject: [PATCH 2/5] Fix mapping and clean up --- .../NHSpecificTest/GH2858/FixtureByCode.cs | 37 ++----------------- .../NHSpecificTest/GH2858/FixtureByCode.cs | 35 ++---------------- 2 files changed, 7 insertions(+), 65 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2858/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2858/FixtureByCode.cs index d02ce91f7ee..ee92242df19 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2858/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2858/FixtureByCode.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by AsyncGenerator. // @@ -10,32 +10,15 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; -using NHibernate.Hql.Ast; -using NHibernate.Linq.Functions; -using NHibernate.Linq.Visitors; using NHibernate.Mapping.ByCode; -using NHibernate.Type; -using NHibernate.Util; using NUnit.Framework; using NHibernate.Linq; namespace NHibernate.Test.NHSpecificTest.GH2858 { using System.Threading.Tasks; - /// - /// Fixture using 'by code' mappings - /// - /// - /// This fixture is identical to except the mapping is performed - /// by code in the GetMappings method, and does not require the Mappings.hbm.xml file. Use this approach - /// if you prefer. - /// [TestFixture] public class ByCodeFixtureAsync : TestCaseMappingByCode { @@ -72,6 +55,7 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.ManyToOne(x => x.Issue, m => m.Column("IssueId")); + rc.Property(x => x.Seconds); }); return mapper.CompileMappingForAllExplicitlyAddedEntities(); @@ -100,7 +84,6 @@ protected override void OnSetUp() var projectZ = new Project {Name = "Z", Department = deptE}; session.Save(projectZ); - var issue1 = new Issue {Name = "TEST-1", Project = projectX,}; session.Save(issue1); var issue2 = new Issue {Name = "TEST-2", Project = projectX, Departments = {deptA},}; @@ -137,12 +120,10 @@ protected override void OnTearDown() } } - [KnownBug("GH-2857")] - [Test] + [Test(Description = "GH-2857")] public async Task GroupLevelQueryAsync() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) { var query = session.Query() .Select(x => new object[] {(object) x}) @@ -151,17 +132,13 @@ public async Task GroupLevelQueryAsync() var results = await (query.ToListAsync()); Assert.That(results, Has.Count.EqualTo(2)); - - await (transaction.RollbackAsync()); } } - [KnownBug("GH-2857")] - [Test] + [Test(Description = "GH-2857")] public async Task GroupLevelQuery_SimplifiedAsync() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) { var query = session.Query() .Select(x => new object[] {x}) @@ -170,8 +147,6 @@ public async Task GroupLevelQuery_SimplifiedAsync() var results = await (query.ToListAsync()); Assert.That(results, Has.Count.EqualTo(2)); - - await (transaction.RollbackAsync()); } } @@ -179,7 +154,6 @@ public async Task GroupLevelQuery_SimplifiedAsync() public async Task SelectManySubQueryWithCoalesceAsync() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) { var usedDepartments = session.Query() .SelectMany(x => ((IEnumerable) x.Issue.Departments).DefaultIfEmpty().Select(d => (object) ((Guid?) ((Guid?) (((IDepartment) d).Id) ?? x.Issue.Project.Department.Id)))) @@ -191,7 +165,6 @@ public async Task SelectManySubQueryWithCoalesceAsync() .Select(d => new {d.Id, d.Name}); Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(4)); - await (transaction.RollbackAsync()); } } @@ -199,7 +172,6 @@ public async Task SelectManySubQueryWithCoalesceAsync() public async Task SelectManySubQueryWithCoalesce_SimplifiedAsync() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) { var usedDepartments = session.Query() .SelectMany(x => ((IEnumerable) x.Issue.Departments).DefaultIfEmpty().Select(d => (Guid?) ((IDepartment) d).Id ?? x.Issue.Project.Department.Id)); @@ -209,7 +181,6 @@ public async Task SelectManySubQueryWithCoalesce_SimplifiedAsync() .Select(d => new {d.Id, d.Name}); Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(4)); - await (transaction.RollbackAsync()); } } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH2858/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH2858/FixtureByCode.cs index 48cb4fdc976..9e4008433e1 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2858/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2858/FixtureByCode.cs @@ -1,29 +1,12 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; -using NHibernate.Hql.Ast; -using NHibernate.Linq.Functions; -using NHibernate.Linq.Visitors; using NHibernate.Mapping.ByCode; -using NHibernate.Type; -using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.GH2858 { - /// - /// Fixture using 'by code' mappings - /// - /// - /// This fixture is identical to except the mapping is performed - /// by code in the GetMappings method, and does not require the Mappings.hbm.xml file. Use this approach - /// if you prefer. - /// [TestFixture] public class ByCodeFixture : TestCaseMappingByCode { @@ -60,6 +43,7 @@ protected override HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.ManyToOne(x => x.Issue, m => m.Column("IssueId")); + rc.Property(x => x.Seconds); }); return mapper.CompileMappingForAllExplicitlyAddedEntities(); @@ -88,7 +72,6 @@ protected override void OnSetUp() var projectZ = new Project {Name = "Z", Department = deptE}; session.Save(projectZ); - var issue1 = new Issue {Name = "TEST-1", Project = projectX,}; session.Save(issue1); var issue2 = new Issue {Name = "TEST-2", Project = projectX, Departments = {deptA},}; @@ -125,12 +108,10 @@ protected override void OnTearDown() } } - [KnownBug("GH-2857")] - [Test] + [Test(Description = "GH-2857")] public void GroupLevelQuery() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) { var query = session.Query() .Select(x => new object[] {(object) x}) @@ -139,17 +120,13 @@ public void GroupLevelQuery() var results = query.ToList(); Assert.That(results, Has.Count.EqualTo(2)); - - transaction.Rollback(); } } - [KnownBug("GH-2857")] - [Test] + [Test(Description = "GH-2857")] public void GroupLevelQuery_Simplified() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) { var query = session.Query() .Select(x => new object[] {x}) @@ -158,8 +135,6 @@ public void GroupLevelQuery_Simplified() var results = query.ToList(); Assert.That(results, Has.Count.EqualTo(2)); - - transaction.Rollback(); } } @@ -167,7 +142,6 @@ public void GroupLevelQuery_Simplified() public void SelectManySubQueryWithCoalesce() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) { var usedDepartments = session.Query() .SelectMany(x => ((IEnumerable) x.Issue.Departments).DefaultIfEmpty().Select(d => (object) ((Guid?) ((Guid?) (((IDepartment) d).Id) ?? x.Issue.Project.Department.Id)))) @@ -179,7 +153,6 @@ public void SelectManySubQueryWithCoalesce() .Select(d => new {d.Id, d.Name}); Assert.That(result.ToList(), Has.Count.EqualTo(4)); - transaction.Rollback(); } } @@ -187,7 +160,6 @@ public void SelectManySubQueryWithCoalesce() public void SelectManySubQueryWithCoalesce_Simplified() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) { var usedDepartments = session.Query() .SelectMany(x => ((IEnumerable) x.Issue.Departments).DefaultIfEmpty().Select(d => (Guid?) ((IDepartment) d).Id ?? x.Issue.Project.Department.Id)); @@ -197,7 +169,6 @@ public void SelectManySubQueryWithCoalesce_Simplified() .Select(d => new {d.Id, d.Name}); Assert.That(result.ToList(), Has.Count.EqualTo(4)); - transaction.Rollback(); } } } From c0c55e0c789e2e84a662b635908689e584afd080 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 3 Aug 2021 07:12:29 +0300 Subject: [PATCH 3/5] Simplify fix --- src/NHibernate/Util/ExpressionsHelper.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/NHibernate/Util/ExpressionsHelper.cs b/src/NHibernate/Util/ExpressionsHelper.cs index 6e3ba896e08..8aad3766e68 100644 --- a/src/NHibernate/Util/ExpressionsHelper.cs +++ b/src/NHibernate/Util/ExpressionsHelper.cs @@ -526,19 +526,12 @@ private static bool TryGetEntityPersister( return persister != null; } - if (convertedType.IsAssignableFrom(currentEntityPersister.MappedClass)) - { - // If it's casting to interface or base class of the same entity - var results = sessionFactory.GetImplementors(convertedType.FullName); - if (results.Length == 1) - { - persister = sessionFactory.TryGetEntityPersister(results[0]); - if (persister != null) - return true; - } - } + if (TryGetEntityPersister(convertedType, sessionFactory, out persister)) + return true; - return TryGetEntityPersister(convertedType, sessionFactory, out persister); + // Assume type conversion doesn't change entity type + persister = currentEntityPersister; + return true; } private static bool TryGetEntityPersister( From d55f4faf15de00e25e4c9f4bada38b6e010f9567 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 4 Aug 2021 10:40:41 +0300 Subject: [PATCH 4/5] More adjustments to be closer to hql processing --- .../Northwind/Entities/Animal.cs | 7 ++++- .../Linq/ParameterTypeLocatorTests.cs | 10 +++++++ src/NHibernate/Util/ExpressionsHelper.cs | 26 ++++++++++++++----- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs b/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs index 384e6e760d5..507a30a96a3 100644 --- a/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs @@ -16,7 +16,12 @@ public class Animal public virtual Animal FatherOrMother => Father ?? Mother; } - public abstract class Reptile : Animal + public interface IReptile + { + EnumStoredAsString Enum1 { get; } + } + + public abstract class Reptile : Animal, IReptile { public virtual double BodyTemperature { get; set; } diff --git a/src/NHibernate.Test/Linq/ParameterTypeLocatorTests.cs b/src/NHibernate.Test/Linq/ParameterTypeLocatorTests.cs index 50be6a9e470..771d5c9fd73 100644 --- a/src/NHibernate.Test/Linq/ParameterTypeLocatorTests.cs +++ b/src/NHibernate.Test/Linq/ParameterTypeLocatorTests.cs @@ -97,6 +97,16 @@ public void SubClassStringEnumTest() ); } + [Test] + public void SubClassAsUnamppedInterfaceStringEnumTest() + { + AssertResults( + new Dictionary> {{"0", o => o is EnumStoredAsStringType}}, + db.Animals.Where(o => o.Children.OfType().Any(r => r.Enum1 == EnumStoredAsString.Unspecified)), + db.Animals.Where(o => o.Children.OfType().Any(r => EnumStoredAsString.Unspecified == r.Enum1)) + ); + } + [Test] public void EqualsMethodStringTest() { diff --git a/src/NHibernate/Util/ExpressionsHelper.cs b/src/NHibernate/Util/ExpressionsHelper.cs index 8aad3766e68..efa12b4e661 100644 --- a/src/NHibernate/Util/ExpressionsHelper.cs +++ b/src/NHibernate/Util/ExpressionsHelper.cs @@ -173,7 +173,14 @@ internal static bool TryGetMappedNullability( return true; } - index = entityPersister.EntityMetamodel.GetPropertyIndex(memberPath); + var propIndex = entityPersister.EntityMetamodel.GetPropertyIndexOrNull(memberPath); + if (propIndex == null) + { + nullable = false; + return false; + } + + index = propIndex.Value; nullable = entityPersister.PropertyNullability[index]; return true; } @@ -338,7 +345,7 @@ private static bool TraverseMembers( // Traverse the members that were traversed by the TryGetAllMemberMetadata method in the reverse order and try to keep // tracking the entity persister until all members are traversed. var member = memberPaths.Pop(); - var currentType = currentEntityPersister.EntityMetamodel.GetPropertyType(member.Path); + var currentType = GetPropertyType(currentEntityPersister, member.Path); IAbstractComponentType currentComponentType = null; while (memberPaths.Count > 0 && currentType != null) { @@ -407,6 +414,12 @@ private static bool TraverseMembers( return false; } + private static IType GetPropertyType(IEntityPersister currentEntityPersister, string path) + { + ((IPropertyMapping) currentEntityPersister).TryToType(path, out var type); + return type; + } + private static IType TryGetComponentPropertyType(IAbstractComponentType componentType, string memberPath) { var index = Array.IndexOf(componentType.PropertyNames, memberPath); @@ -471,7 +484,7 @@ private static void ProcessAssociationType( memberComponent = null; memberType = memberPersister != null - ? memberPersister.EntityMetamodel.GetPropertyType(member.Path) + ? GetPropertyType(memberPersister, member.Path) : null; // q.AnyType.Member, ((NotMappedClass)q.ManyToOne) } @@ -523,13 +536,14 @@ private static bool TryGetEntityPersister( .Select(sessionFactory.GetEntityPersister) .FirstOrDefault(p => p.MappedClass == convertedType); - return persister != null; + if (persister != null) + return true; } - - if (TryGetEntityPersister(convertedType, sessionFactory, out persister)) + else if (TryGetEntityPersister(convertedType, sessionFactory, out persister)) return true; // Assume type conversion doesn't change entity type + // TODO: Consider removing convertedType related logic above and always return currentEntityPersister persister = currentEntityPersister; return true; } From 7c45c191fb8b6661cf7ce8076115c71381feebef Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 4 Aug 2021 10:54:16 +0300 Subject: [PATCH 5/5] Fix test --- src/NHibernate.Test/Linq/TryGetMappedTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate.Test/Linq/TryGetMappedTests.cs b/src/NHibernate.Test/Linq/TryGetMappedTests.cs index 880ce2e3e69..964072efdc2 100644 --- a/src/NHibernate.Test/Linq/TryGetMappedTests.cs +++ b/src/NHibernate.Test/Linq/TryGetMappedTests.cs @@ -124,7 +124,7 @@ public void SelfCastNotMappedTest() false, typeof(A).FullName, null, - o => o is SerializableType serializableType && serializableType.ReturnedClass == typeof(object)); + o => o is EntityType entityType && entityType.ReturnedClass == typeof(A)); } [Test]