From 494917424e599e702d2c0540c10a8fa9065f6448 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 1 Aug 2023 22:23:04 +0300 Subject: [PATCH] Use table group joins for many-to-many in Criteria and Entity loaders (#2687) Possible breaking change: Default not-found behavior now works correctly for many-to-many in Criteria so now it throws ObjectNotFoundException exception on many-to-many Criteria fetch for not found records. --- ...ixture.cs => ManyToManyFilteredFixture.cs} | 39 +++-- ....cs => ManyToManyNotFoundIgnoreFixture.cs} | 136 ++++++++++------ .../ManyToManyThrowsForNotFoundFixture.cs | 85 ++++++++++ .../ManyToManyPropertyRefFixture.cs | 23 ++- ...ixture.cs => ManyToManyFilteredFixture.cs} | 39 +++-- .../NHSpecificTest/GH1994/Mappings.hbm.xml | 6 +- .../NHSpecificTest/NH750/Device.cs | 9 +- .../NHSpecificTest/NH750/Fixture.cs | 113 ------------- .../NH750/ManyToManyNotFoundIgnoreFixture.cs | 151 ++++++++++++++++++ .../ManyToManyThrowsForNotFoundFixture.cs | 74 +++++++++ .../NHSpecificTest/NH750/Mappings.hbm.xml | 6 +- .../ManyToManyPropertyRefFixture.cs | 23 ++- .../Loader/AbstractEntityJoinWalker.cs | 2 +- src/NHibernate/Loader/JoinWalker.cs | 68 ++++---- .../Loader/OuterJoinableAssociation.cs | 14 +- .../Collection/AbstractCollectionPersister.cs | 9 +- .../Collection/BasicCollectionPersister.cs | 44 +---- .../Collection/OneToManyPersister.cs | 8 +- .../Entity/AbstractEntityPersister.cs | 9 +- .../Entity/ISupportSelectModeJoinable.cs | 2 +- 20 files changed, 581 insertions(+), 279 deletions(-) rename src/NHibernate.Test/Async/NHSpecificTest/GH1994/{Fixture.cs => ManyToManyFilteredFixture.cs} (83%) rename src/NHibernate.Test/Async/NHSpecificTest/NH750/{Fixture.cs => ManyToManyNotFoundIgnoreFixture.cs} (51%) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs rename src/NHibernate.Test/NHSpecificTest/GH1994/{Fixture.cs => ManyToManyFilteredFixture.cs} (82%) delete mode 100644 src/NHibernate.Test/NHSpecificTest/NH750/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs similarity index 83% rename from src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs rename to src/NHibernate.Test/Async/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs index ff47bd2ad14..0c930f421f3 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs @@ -10,7 +10,6 @@ using System.Linq; using NHibernate.Criterion; -using NHibernate.Dialect; using NHibernate.Linq; using NHibernate.SqlCommand; using NHibernate.Transform; @@ -20,7 +19,7 @@ namespace NHibernate.Test.NHSpecificTest.GH1994 { using System.Threading.Tasks; [TestFixture] - public class FixtureAsync : BugTestCase + public class ManyToManyFilteredFixtureAsync : BugTestCase { protected override void OnSetUp() { @@ -41,14 +40,7 @@ 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(); } } @@ -70,9 +62,6 @@ public async Task TestUnfilteredLinqQueryAsync() [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() @@ -150,5 +139,31 @@ public async Task TestQueryOverRestrictionWithClauseAsync() Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); } } + + [Test] + public async Task LazyLoadAsync() + { + using (var s = OpenSession()) + { + var asset = await (s.Query().FirstAsync()); + Assert.That(asset.Documents.Count, Is.EqualTo(2)); + Assert.That(asset.DocumentsBag.Count, Is.EqualTo(2)); + Assert.That(asset.DocumentsFiltered.Count, Is.EqualTo(1)); + } + } + + [Test] + public async Task LazyLoadFilteredAsync() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + var asset = await (s.Query().FirstAsync()); + Assert.That(asset.Documents.Count, Is.EqualTo(1)); + Assert.That(asset.DocumentsBag.Count, Is.EqualTo(1)); + Assert.That(asset.DocumentsFiltered.Count, Is.EqualTo(1)); + } + } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH750/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs similarity index 51% rename from src/NHibernate.Test/Async/NHSpecificTest/NH750/Fixture.cs rename to src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs index 07df8065629..2ab73df8d3a 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH750/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs @@ -10,78 +10,72 @@ using System; using NHibernate.Cfg; +using NHibernate.Criterion; +using NHibernate.Transform; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.NH750 { using System.Threading.Tasks; [TestFixture] - public class FixtureAsync : BugTestCase + public class ManyToManyNotFoundIgnoreFixtureAsync : BugTestCase { - protected override void OnTearDown() - { - using (ISession s = Sfi.OpenSession()) - { - s.Delete("from Device"); - s.Delete("from Drive"); - s.Flush(); - } - } + private int id1; + private int id2; - protected override void Configure(Configuration configuration) + protected override void OnSetUp() { - configuration.SetProperty(Cfg.Environment.UseSecondLevelCache, "false"); - base.Configure(configuration); - } - - [Test] - public async Task DeviceOfDriveAsync() - { - int[] dvSavedId = new int[2]; Drive dr1 = new Drive("Drive 1"); Drive dr2 = new Drive("Drive 2"); Drive dr3 = new Drive("Drive 3"); Device dv1 = new Device("Device 1"); Device dv2 = new Device("Device 2"); - using (ISession s = Sfi.OpenSession()) + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) { - await (s.SaveAsync(dr1)); - await (s.SaveAsync(dr2)); - await (s.SaveAsync(dr3)); - dvSavedId[0] = (int) await (s.SaveAsync(dv1)); - dvSavedId[1] = (int) await (s.SaveAsync(dv2)); - await (s.FlushAsync()); + s.Save(dr1); + s.Save(dr2); + s.Save(dr3); + dv1.Drives.Add(dr1); + dv1.Drives.Add(dr2); + dv2.Drives.Add(dr1); + dv2.Drives.Add(dr3); + + id1 = (int) s.Save(dv1); + id2 = (int) s.Save(dv2); + s.Flush(); + + s.Clear(); + s.Delete(dr3); + t.Commit(); } + } - dv1.Drives.Add(dr1); - dv1.Drives.Add(dr2); - dv2.Drives.Add(dr1); - dv2.Drives.Add(dr3); + protected override void OnTearDown() + { using (ISession s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) { - dvSavedId[0] = (int) await (s.SaveAsync(dv1)); - dvSavedId[1] = (int) await (s.SaveAsync(dv2)); - await (s.FlushAsync()); + s.Delete("from Device"); + s.Delete("from Drive"); + t.Commit(); } - dv1 = null; - dv2 = null; + } + + [Test] + public async Task DeviceOfDriveAsync() + { + Device dv1; + Device dv2; using (ISession s = Sfi.OpenSession()) { - await (s.DeleteAsync(dr3)); - await (s.FlushAsync()); - dv1 = (Device) await (s.LoadAsync(typeof(Device), dvSavedId[0])); - dv2 = (Device) await (s.LoadAsync(typeof(Device), dvSavedId[1])); + dv1 = (Device) await (s.LoadAsync(typeof(Device), id1)); + dv2 = (Device) await (s.LoadAsync(typeof(Device), id2)); } - Assert.AreEqual(2, dv1.Drives.Count); - // Verify one is missing - Assert.AreEqual(1, dv2.Drives.Count); - // Verify dv1 unchanged - Assert.IsTrue(dv1.Drives.Contains(dr1)); - Assert.IsTrue(dv1.Drives.Contains(dr2)); - // Verify dv2 - Assert.IsTrue(dv2.Drives.Contains(dr1)); - Assert.IsFalse(dv2.Drives.Contains(dr3)); + Assert.That(dv1.Drives, Has.Count.EqualTo(2).And.None.Null); + // Verify one is missing + Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null); //Make sure that flush didn't touch not-found="ignore" records for not modified collection using (var s = Sfi.OpenSession()) @@ -99,7 +93,7 @@ public async Task DeviceOfDriveAsync() using (var t = s.BeginTransaction()) { dv2 = await (s.GetAsync(dv2.Id)); - dv2.Drives.Add(dr2); + dv2.Drives.Add(dv1.Drives[1]); await (t.CommitAsync()); } @@ -120,5 +114,49 @@ async Task VerifyResultAsync(int expectedInCollection, int expectedInDb, string } } } + + [Test] + public async Task QueryOverFetchAsync() + { + using (var s = OpenSession()) + { + var dv2 = await (s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Drives) + .Where(Restrictions.IdEq(id2)) + .TransformUsing(Transformers.DistinctRootEntity) + .SingleOrDefaultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True); + Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null); + } + } + + [Test] + public async Task HqlFetchAsync() + { + using (var s = OpenSession()) + { + var dv2 = await (s.CreateQuery("from Device d left join fetch d.Drives where d.id = :id") + .SetResultTransformer(Transformers.DistinctRootEntity) + .SetParameter("id", id2) + .UniqueResultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True); + Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null); + } + } + + [Test] + public async Task LazyLoadAsync() + { + using (var s = OpenSession()) + { + var dv2 = await (s.GetAsync(id2)); + await (NHibernateUtil.InitializeAsync(dv2.Drives)); + + Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True); + Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null); + } + } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs new file mode 100644 index 00000000000..59b6a0044bf --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +// +// 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.Criterion; +using NHibernate.Linq; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH750 +{ + using System.Threading.Tasks; + [TestFixture] + public class ManyToManyThrowsForNotFoundFixtureAsync : BugTestCase + { + private int _id; + + protected override void OnSetUp() + { + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + Device dv = new Device("Device"); + Drive dr = new Drive("Drive"); + s.Save(dr); + dv.DrivesNotIgnored.Add(dr); + + _id = (int) s.Save(dv); + s.Flush(); + + s.Clear(); + s.Delete(dr); + t.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from Device"); + s.Delete("from Drive"); + t.Commit(); + } + } + + [Test] + public async Task LazyLoadAsync() + { + using var s = OpenSession(); + var device = await (s.GetAsync(_id)); + Assert.ThrowsAsync(() => NHibernateUtil.InitializeAsync(device.DrivesNotIgnored)); + } + + [Test] + public void QueryOverFetchAsync() + { + using var s = OpenSession(); + var queryOver = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.DrivesNotIgnored) + .Where(Restrictions.IdEq(_id)) + .TransformUsing(Transformers.DistinctRootEntity); + Assert.ThrowsAsync(async () => await (NHibernateUtil.InitializeAsync(await (queryOver.SingleOrDefaultAsync())))); + } + + [Test] + public void LinqFetchAsync() + { + using var s = OpenSession(); + var query = s.Query() + + .Fetch(x => x.DrivesNotIgnored) + .Where(x => x.Id == _id); + Assert.ThrowsAsync(async () => await (NHibernateUtil.InitializeAsync(await (query.SingleOrDefaultAsync())))); + } + } +} diff --git a/src/NHibernate.Test/Async/PropertyRef/ManyToManyPropertyRefFixture.cs b/src/NHibernate.Test/Async/PropertyRef/ManyToManyPropertyRefFixture.cs index 1bb2716733f..435758ac99e 100644 --- a/src/NHibernate.Test/Async/PropertyRef/ManyToManyPropertyRefFixture.cs +++ b/src/NHibernate.Test/Async/PropertyRef/ManyToManyPropertyRefFixture.cs @@ -8,20 +8,22 @@ //------------------------------------------------------------------------------ +using System.Linq; using NHibernate.Criterion; +using NHibernate.Linq; using NUnit.Framework; namespace NHibernate.Test.PropertyRef { using System.Threading.Tasks; - [TestFixture] + [TestFixture(Description = "NH-2180 (GH-1214)")] public class ManyToManyPropertyRefFixtureAsync : TestCase { protected override string[] Mappings => new[] { "PropertyRef.ManyToManyWithPropertyRef.hbm.xml" }; protected override string MappingsAssembly => "NHibernate.Test"; - private object _manyAId; + private long _manyAId; protected override void OnSetUp() { @@ -34,7 +36,7 @@ protected override void OnSetUp() var manyB2 = new ManyB { Number = 8, Value = "a value of b2" }; var manyB3 = new ManyB { Number = 12, Value = "a value of b3" }; - _manyAId = session.Save(manyA); + _manyAId = (long) session.Save(manyA); session.Save(manyB1); session.Save(manyB2); session.Save(manyB3); @@ -144,5 +146,20 @@ bei NHibernate.Type.EntityType.LoadByUniqueKey(String entityName, String uniqueK Assert.That(loadedManyA.ManyBs, Has.Count.EqualTo(3).And.None.Null); } + + [Test] + public async Task LinqFetchAsync() + { + using (var session = OpenSession()) + { + var manyA = (await (session + .Query() + .Where(a => a.Id == _manyAId) + .FetchMany(a => a.ManyBs) + .ToListAsync())) + .First(); + Assert.That(manyA.ManyBs, Has.Count.EqualTo(3).And.None.Null); + } + } } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs similarity index 82% rename from src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs rename to src/NHibernate.Test/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs index 5dbe043ebfe..4edb09d5cb1 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs @@ -1,6 +1,5 @@ using System.Linq; using NHibernate.Criterion; -using NHibernate.Dialect; using NHibernate.Linq; using NHibernate.SqlCommand; using NHibernate.Transform; @@ -9,7 +8,7 @@ namespace NHibernate.Test.NHSpecificTest.GH1994 { [TestFixture] - public class Fixture : BugTestCase + public class ManyToManyFilteredFixture : BugTestCase { protected override void OnSetUp() { @@ -30,14 +29,7 @@ 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(); } } @@ -59,9 +51,6 @@ public void TestUnfilteredLinqQuery() [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() @@ -139,5 +128,31 @@ public void TestQueryOverRestrictionWithClause() Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); } } + + [Test] + public void LazyLoad() + { + using (var s = OpenSession()) + { + var asset = s.Query().First(); + Assert.That(asset.Documents.Count, Is.EqualTo(2)); + Assert.That(asset.DocumentsBag.Count, Is.EqualTo(2)); + Assert.That(asset.DocumentsFiltered.Count, Is.EqualTo(1)); + } + } + + [Test] + public void LazyLoadFiltered() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + var asset = s.Query().First(); + Assert.That(asset.Documents.Count, Is.EqualTo(1)); + Assert.That(asset.DocumentsBag.Count, Is.EqualTo(1)); + Assert.That(asset.DocumentsFiltered.Count, Is.EqualTo(1)); + } + } } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml index 449a4b8bc97..dba718ccde7 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml @@ -5,7 +5,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -39,7 +39,7 @@ - + diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/Device.cs b/src/NHibernate.Test/NHSpecificTest/NH750/Device.cs index 7a77e8b2e04..897045acaf6 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH750/Device.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH750/Device.cs @@ -33,11 +33,18 @@ public string Manifacturer } private IList _drives = new List(); + private IList _drivesNotIgnored = new List(); public IList Drives { get { return _drives; } set { _drives = value; } } + + public IList DrivesNotIgnored + { + get => _drivesNotIgnored; + set => _drivesNotIgnored = value; + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH750/Fixture.cs deleted file mode 100644 index b766cd8b87e..00000000000 --- a/src/NHibernate.Test/NHSpecificTest/NH750/Fixture.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using NHibernate.Cfg; -using NUnit.Framework; - -namespace NHibernate.Test.NHSpecificTest.NH750 -{ - [TestFixture] - public class Fixture : BugTestCase - { - protected override void OnTearDown() - { - using (ISession s = Sfi.OpenSession()) - { - s.Delete("from Device"); - s.Delete("from Drive"); - s.Flush(); - } - } - - protected override void Configure(Configuration configuration) - { - configuration.SetProperty(Cfg.Environment.UseSecondLevelCache, "false"); - base.Configure(configuration); - } - - [Test] - public void DeviceOfDrive() - { - int[] dvSavedId = new int[2]; - Drive dr1 = new Drive("Drive 1"); - Drive dr2 = new Drive("Drive 2"); - Drive dr3 = new Drive("Drive 3"); - Device dv1 = new Device("Device 1"); - Device dv2 = new Device("Device 2"); - using (ISession s = Sfi.OpenSession()) - { - s.Save(dr1); - s.Save(dr2); - s.Save(dr3); - dvSavedId[0] = (int) s.Save(dv1); - dvSavedId[1] = (int) s.Save(dv2); - s.Flush(); - } - - dv1.Drives.Add(dr1); - dv1.Drives.Add(dr2); - dv2.Drives.Add(dr1); - dv2.Drives.Add(dr3); - using (ISession s = Sfi.OpenSession()) - { - dvSavedId[0] = (int) s.Save(dv1); - dvSavedId[1] = (int) s.Save(dv2); - s.Flush(); - } - dv1 = null; - dv2 = null; - using (ISession s = Sfi.OpenSession()) - { - s.Delete(dr3); - s.Flush(); - dv1 = (Device) s.Load(typeof(Device), dvSavedId[0]); - dv2 = (Device) s.Load(typeof(Device), dvSavedId[1]); - } - Assert.AreEqual(2, dv1.Drives.Count); - // Verify one is missing - Assert.AreEqual(1, dv2.Drives.Count); - // Verify dv1 unchanged - Assert.IsTrue(dv1.Drives.Contains(dr1)); - Assert.IsTrue(dv1.Drives.Contains(dr2)); - - // Verify dv2 - Assert.IsTrue(dv2.Drives.Contains(dr1)); - Assert.IsFalse(dv2.Drives.Contains(dr3)); - - //Make sure that flush didn't touch not-found="ignore" records for not modified collection - using (var s = Sfi.OpenSession()) - using (var t = s.BeginTransaction()) - { - dv2 = s.Get(dv2.Id); - s.Flush(); - t.Commit(); - } - - VerifyResult(expectedInCollection: 1, expectedInDb: 2, msg: "not modified collection"); - - //Many-to-many clears collection and recreates it so not-found ignore records are lost - using (var s = Sfi.OpenSession()) - using (var t = s.BeginTransaction()) - { - dv2 = s.Get(dv2.Id); - dv2.Drives.Add(dr2); - t.Commit(); - } - - VerifyResult(2, 2, msg: "modified collection"); - - void VerifyResult(int expectedInCollection, int expectedInDb, string msg) - { - using (var s = Sfi.OpenSession()) - { - var realCound = Convert.ToInt32( - s.CreateSQLQuery("select count(*) from DriveOfDevice where DeviceId = :id ") - .SetParameter("id", dv2.Id) - .UniqueResult()); - dv2 = s.Get(dv2.Id); - - Assert.That(dv2.Drives.Count, Is.EqualTo(expectedInCollection), msg); - Assert.That(realCound, Is.EqualTo(expectedInDb), msg); - } - } - } - } -} diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs new file mode 100644 index 00000000000..ecff1b3486b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs @@ -0,0 +1,151 @@ +using System; +using NHibernate.Cfg; +using NHibernate.Criterion; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH750 +{ + [TestFixture] + public class ManyToManyNotFoundIgnoreFixture : BugTestCase + { + private int id1; + private int id2; + + protected override void OnSetUp() + { + Drive dr1 = new Drive("Drive 1"); + Drive dr2 = new Drive("Drive 2"); + Drive dr3 = new Drive("Drive 3"); + Device dv1 = new Device("Device 1"); + Device dv2 = new Device("Device 2"); + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save(dr1); + s.Save(dr2); + s.Save(dr3); + dv1.Drives.Add(dr1); + dv1.Drives.Add(dr2); + dv2.Drives.Add(dr1); + dv2.Drives.Add(dr3); + + id1 = (int) s.Save(dv1); + id2 = (int) s.Save(dv2); + s.Flush(); + + s.Clear(); + s.Delete(dr3); + t.Commit(); + } + } + + protected override void OnTearDown() + { + using (ISession s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from Device"); + s.Delete("from Drive"); + t.Commit(); + } + } + + [Test] + public void DeviceOfDrive() + { + Device dv1; + Device dv2; + using (ISession s = Sfi.OpenSession()) + { + dv1 = (Device) s.Load(typeof(Device), id1); + dv2 = (Device) s.Load(typeof(Device), id2); + } + + Assert.That(dv1.Drives, Has.Count.EqualTo(2).And.None.Null); + // Verify one is missing + Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null); + + //Make sure that flush didn't touch not-found="ignore" records for not modified collection + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + dv2 = s.Get(dv2.Id); + s.Flush(); + t.Commit(); + } + + VerifyResult(expectedInCollection: 1, expectedInDb: 2, msg: "not modified collection"); + + //Many-to-many clears collection and recreates it so not-found ignore records are lost + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + dv2 = s.Get(dv2.Id); + dv2.Drives.Add(dv1.Drives[1]); + t.Commit(); + } + + VerifyResult(2, 2, msg: "modified collection"); + + void VerifyResult(int expectedInCollection, int expectedInDb, string msg) + { + using (var s = Sfi.OpenSession()) + { + var realCound = Convert.ToInt32( + s.CreateSQLQuery("select count(*) from DriveOfDevice where DeviceId = :id ") + .SetParameter("id", dv2.Id) + .UniqueResult()); + dv2 = s.Get(dv2.Id); + + Assert.That(dv2.Drives.Count, Is.EqualTo(expectedInCollection), msg); + Assert.That(realCound, Is.EqualTo(expectedInDb), msg); + } + } + } + + [Test] + public void QueryOverFetch() + { + using (var s = OpenSession()) + { + var dv2 = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Drives) + .Where(Restrictions.IdEq(id2)) + .TransformUsing(Transformers.DistinctRootEntity) + .SingleOrDefault(); + + Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True); + Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null); + } + } + + [Test] + public void HqlFetch() + { + using (var s = OpenSession()) + { + var dv2 = s.CreateQuery("from Device d left join fetch d.Drives where d.id = :id") + .SetResultTransformer(Transformers.DistinctRootEntity) + .SetParameter("id", id2) + .UniqueResult(); + + Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True); + Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null); + } + } + + [Test] + public void LazyLoad() + { + using (var s = OpenSession()) + { + var dv2 = s.Get(id2); + NHibernateUtil.Initialize(dv2.Drives); + + Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True); + Assert.That(dv2.Drives, Has.Count.EqualTo(1).And.None.Null); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs new file mode 100644 index 00000000000..bef312c398c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs @@ -0,0 +1,74 @@ +using System.Linq; +using NHibernate.Criterion; +using NHibernate.Linq; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH750 +{ + [TestFixture] + public class ManyToManyThrowsForNotFoundFixture : BugTestCase + { + private int _id; + + protected override void OnSetUp() + { + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + Device dv = new Device("Device"); + Drive dr = new Drive("Drive"); + s.Save(dr); + dv.DrivesNotIgnored.Add(dr); + + _id = (int) s.Save(dv); + s.Flush(); + + s.Clear(); + s.Delete(dr); + t.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from Device"); + s.Delete("from Drive"); + t.Commit(); + } + } + + [Test] + public void LazyLoad() + { + using var s = OpenSession(); + var device = s.Get(_id); + Assert.Throws(() => NHibernateUtil.Initialize(device.DrivesNotIgnored)); + } + + [Test] + public void QueryOverFetch() + { + using var s = OpenSession(); + var queryOver = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.DrivesNotIgnored) + .Where(Restrictions.IdEq(_id)) + .TransformUsing(Transformers.DistinctRootEntity); + Assert.Throws(() => NHibernateUtil.Initialize(queryOver.SingleOrDefault())); + } + + [Test] + public void LinqFetch() + { + using var s = OpenSession(); + var query = s.Query() + + .Fetch(x => x.DrivesNotIgnored) + .Where(x => x.Id == _id); + Assert.Throws(() => NHibernateUtil.Initialize(query.SingleOrDefault())); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH750/Mappings.hbm.xml index 6a100d7926d..56d7c9c5927 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH750/Mappings.hbm.xml +++ b/src/NHibernate.Test/NHSpecificTest/NH750/Mappings.hbm.xml @@ -5,7 +5,7 @@ default-access="field.camelcase-underscore" default-lazy="false"> - + @@ -13,6 +13,10 @@ + + + + diff --git a/src/NHibernate.Test/PropertyRef/ManyToManyPropertyRefFixture.cs b/src/NHibernate.Test/PropertyRef/ManyToManyPropertyRefFixture.cs index 054cba44957..9ecb5dd4377 100644 --- a/src/NHibernate.Test/PropertyRef/ManyToManyPropertyRefFixture.cs +++ b/src/NHibernate.Test/PropertyRef/ManyToManyPropertyRefFixture.cs @@ -1,16 +1,18 @@ +using System.Linq; using NHibernate.Criterion; +using NHibernate.Linq; using NUnit.Framework; namespace NHibernate.Test.PropertyRef { - [TestFixture] + [TestFixture(Description = "NH-2180 (GH-1214)")] public class ManyToManyPropertyRefFixture : TestCase { protected override string[] Mappings => new[] { "PropertyRef.ManyToManyWithPropertyRef.hbm.xml" }; protected override string MappingsAssembly => "NHibernate.Test"; - private object _manyAId; + private long _manyAId; protected override void OnSetUp() { @@ -23,7 +25,7 @@ protected override void OnSetUp() var manyB2 = new ManyB { Number = 8, Value = "a value of b2" }; var manyB3 = new ManyB { Number = 12, Value = "a value of b3" }; - _manyAId = session.Save(manyA); + _manyAId = (long) session.Save(manyA); session.Save(manyB1); session.Save(manyB2); session.Save(manyB3); @@ -133,5 +135,20 @@ bei NHibernate.Type.EntityType.LoadByUniqueKey(String entityName, String uniqueK Assert.That(loadedManyA.ManyBs, Has.Count.EqualTo(3).And.None.Null); } + + [Test] + public void LinqFetch() + { + using (var session = OpenSession()) + { + var manyA = session + .Query() + .Where(a => a.Id == _manyAId) + .FetchMany(a => a.ManyBs) + .ToList() + .First(); + Assert.That(manyA.ManyBs, Has.Count.EqualTo(3).And.None.Null); + } + } } } diff --git a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs index 4eb40d53210..606c55423d1 100644 --- a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs +++ b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs @@ -121,7 +121,7 @@ private void InitStatementString(OuterJoinableAssociation rootAssociation, SqlSt Suffixes = BasicLoader.GenerateSuffixes(joins + 1); var suffix = Suffixes[joins]; - selectClause = new SqlString(rootAssociation.GetSelectFragment(suffix, null, null) + SelectString(associations)); + selectClause = new SqlString(rootAssociation.GetSelectFragment(suffix, null) + SelectString(associations)); } JoinFragment ojf = MergeOuterJoins(associations); diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index e2650582718..5fa910eb48f 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using NHibernate.Collection; using NHibernate.Engine; @@ -884,41 +883,51 @@ protected JoinFragment MergeOuterJoins(IList associati JoinFragment outerjoin = Dialect.CreateOuterJoinFragment(); var sortedAssociations = GetSortedAssociations(associations); - OuterJoinableAssociation last = null; - foreach (OuterJoinableAssociation oj in sortedAssociations) + for (var index = 0; index < sortedAssociations.Count; index++) { - if (last != null && last.IsManyToManyWith(oj)) + OuterJoinableAssociation oj = sortedAssociations[index]; + if (oj.IsCollection && oj.Joinable is IQueryableCollection qc && qc.IsManyToMany && index < sortedAssociations.Count - 1) { - oj.AddManyToManyJoin(outerjoin, (IQueryableCollection) last.Joinable); + var entityAssociation = sortedAssociations[index + 1]; + var f = qc.GetManyToManyFilterFragment(entityAssociation.RHSAlias, enabledFilters); + if (oj.IsManyToManyWith(entityAssociation) + && TableGroupJoinHelper.ProcessAsTableGroupJoin( + new[] {oj, entityAssociation}, + new[] {oj.On, entityAssociation.On, string.IsNullOrEmpty(f) ? SqlString.Empty : new SqlString(f)}, + true, + outerjoin, + alias => true, + factory)) + { + index++; + continue; + } } - else + + // NH Different behavior : NH1179 and NH1293 + // Apply filters for entity joins and Many-To-One associations + SqlString filter = null; + var enabledFiltersForJoin = oj.ForceFilter ? enabledFilters : enabledFiltersForManyToOne; + if (oj.ForceFilter || enabledFiltersForJoin.Count > 0) { - // NH Different behavior : NH1179 and NH1293 - // Apply filters for entity joins and Many-To-One associations - SqlString filter = null; - var enabledFiltersForJoin = oj.ForceFilter ? enabledFilters : enabledFiltersForManyToOne; - if (oj.ForceFilter || enabledFiltersForJoin.Count > 0) + string manyToOneFilterFragment = oj.Joinable.FilterFragment(oj.RHSAlias, enabledFiltersForJoin); + bool joinClauseDoesNotContainsFilterAlready = + oj.On?.IndexOfCaseInsensitive(manyToOneFilterFragment) == -1; + if (joinClauseDoesNotContainsFilterAlready) { - string manyToOneFilterFragment = oj.Joinable.FilterFragment(oj.RHSAlias, enabledFiltersForJoin); - bool joinClauseDoesNotContainsFilterAlready = - oj.On?.IndexOfCaseInsensitive(manyToOneFilterFragment) == -1; - if (joinClauseDoesNotContainsFilterAlready) - { - filter = new SqlString(manyToOneFilterFragment); - } + filter = new SqlString(manyToOneFilterFragment); } + } - if (TableGroupJoinHelper.ProcessAsTableGroupJoin(new[] {oj}, new[] {oj.On, filter}, true, outerjoin, alias => true, factory)) - continue; + if (TableGroupJoinHelper.ProcessAsTableGroupJoin(new[] {oj}, new[] {oj.On, filter}, true, outerjoin, alias => true, factory)) + continue; - oj.AddJoins(outerjoin); + oj.AddJoins(outerjoin); - // Ensure that the join condition is added to the join, not the where clause. - // Adding the condition to the where clause causes left joins to become inner joins. - if (SqlStringHelper.IsNotEmpty(filter)) - outerjoin.AddFromFragmentString(filter); - } - last = oj; + // Ensure that the join condition is added to the join, not the where clause. + // Adding the condition to the where clause causes left joins to become inner joins. + if (SqlStringHelper.IsNotEmpty(filter)) + outerjoin.AddFromFragmentString(filter); } return outerjoin; @@ -1212,7 +1221,6 @@ public string SelectString(IList associations) for (int i = 0; i < associations.Count; i++) { OuterJoinableAssociation join = associations[i]; - OuterJoinableAssociation next = (i == associations.Count - 1) ? null : associations[i + 1]; IJoinable joinable = join.Joinable; string entitySuffix = (suffixes == null || entityAliasCount >= suffixes.Length) ? null : suffixes[entityAliasCount]; @@ -1221,7 +1229,7 @@ public string SelectString(IList associations) ? null : collectionSuffixes[collectionAliasCount]; - string selectFragment = join.GetSelectFragment(entitySuffix, collectionSuffix, next); + string selectFragment = join.GetSelectFragment(entitySuffix, collectionSuffix); if (!string.IsNullOrWhiteSpace(selectFragment)) { @@ -1243,7 +1251,7 @@ public string SelectString(IList associations) [Obsolete("This method has no more usages and will be removed in a future version")] protected static string GetSelectFragment(OuterJoinableAssociation join, string entitySuffix, string collectionSuffix, OuterJoinableAssociation next = null) { - return join.GetSelectFragment(entitySuffix, collectionSuffix, next); + return join.GetSelectFragment(entitySuffix, collectionSuffix); } protected interface IJoinQueueEntry diff --git a/src/NHibernate/Loader/OuterJoinableAssociation.cs b/src/NHibernate/Loader/OuterJoinableAssociation.cs index 47a2e01e613..74180f89408 100644 --- a/src/NHibernate/Loader/OuterJoinableAssociation.cs +++ b/src/NHibernate/Loader/OuterJoinableAssociation.cs @@ -169,6 +169,8 @@ public bool IsManyToManyWith(OuterJoinableAssociation other) return false; } + //Since 5.4 + [Obsolete("This method is not used anymore and will be removed in a next major version")] public void AddManyToManyJoin(JoinFragment outerjoin, IQueryableCollection collection) { string manyToManyFilter = collection.GetManyToManyFilterFragment(rhsAlias, enabledFilters); @@ -204,7 +206,7 @@ internal bool ShouldFetchCollectionPersister() throw new ArgumentOutOfRangeException(nameof(SelectMode), SelectMode.ToString()); } - internal string GetSelectFragment(string entitySuffix, string collectionSuffix, OuterJoinableAssociation next) + internal string GetSelectFragment(string entitySuffix, string collectionSuffix) { switch (SelectMode) { @@ -212,8 +214,8 @@ internal string GetSelectFragment(string entitySuffix, string collectionSuffix, case SelectMode.Fetch: #pragma warning disable 618 return Joinable.SelectFragment( - next?.Joinable, - next?.RHSAlias, + null, + null, RHSAlias, entitySuffix, collectionSuffix, @@ -224,8 +226,8 @@ internal string GetSelectFragment(string entitySuffix, string collectionSuffix, #pragma warning disable 618 return ReflectHelper.CastOrThrow(Joinable, "fetch lazy properties") .SelectFragment( - next?.Joinable, - next?.RHSAlias, + null, + null, RHSAlias, entitySuffix, collectionSuffix, @@ -236,8 +238,6 @@ internal string GetSelectFragment(string entitySuffix, string collectionSuffix, case SelectMode.FetchLazyPropertyGroup: return ReflectHelper.CastOrThrow(Joinable, "fetch lazy property") .SelectFragment( - next?.Joinable, - next?.RHSAlias, RHSAlias, collectionSuffix, ShouldFetchCollectionPersister(), diff --git a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs index 612900b989b..a5c18cf92cb 100644 --- a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs @@ -1777,8 +1777,15 @@ public virtual string SelectFragment( return SelectFragment(rhs, rhsAlias, lhsAlias, collectionSuffix, includeCollectionColumns, new EntityLoadInfo(entitySuffix) {IncludeLazyProps = true}); } - //6.0 TODO: Make abstract + // 6.0 TODO: Remove + [Obsolete("Please use overload without rhs and rhsAlias parameters")] public virtual string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string currentCollectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) + { + return SelectFragment(lhsAlias, currentCollectionSuffix, includeCollectionColumns, entityInfo); + } + + // 6.0 TODO: Make abstract + public virtual string SelectFragment(string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { throw new NotImplementedException("SelectFragment with fetching lazy properties option is not implemented by " + GetType().FullName); } diff --git a/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs b/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs index 65eb87b0724..d28a1a5b7ee 100644 --- a/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs @@ -262,51 +262,15 @@ protected override int DoUpdateRows(object id, IPersistentCollection collection, } } + [Obsolete("Please use overload without rhs and rhsAlias parameters")] public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { - // we need to determine the best way to know that two joinables - // represent a single many-to-many... - if (rhs != null && IsManyToMany && !rhs.IsCollection) - { - IAssociationType elementType = (IAssociationType) ElementType; - if (rhs.Equals(elementType.GetAssociatedJoinable(Factory))) - { - return ManyToManySelectFragment(rhs, rhsAlias, lhsAlias, collectionSuffix, elementType); - } - } - return includeCollectionColumns - ? GetSelectFragment(lhsAlias, collectionSuffix).ToSqlStringFragment(false) - : string.Empty; + return SelectFragment(lhsAlias, collectionSuffix, includeCollectionColumns, entityInfo); } - private string ManyToManySelectFragment( - IJoinable rhs, - string rhsAlias, - string lhsAlias, - string collectionSuffix, - IAssociationType elementType) + public override string SelectFragment(string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { - SelectFragment frag = GenerateSelectFragment(lhsAlias, collectionSuffix); - - // We need to select in the associated entity table instead of taking the collection actual element, - // because filters can be applied to the entity table outer join. In such case, we need to return null - // for filtered-out elements. (It is tempting to switch to an inner join and just use - // SelectFragment(lhsAlias, collectionSuffix) for many-to-many too, but this would hinder the proper - // handling of the not-found feature.) - var elementColumnNames = string.IsNullOrEmpty(elementType.RHSUniqueKeyPropertyName) - ? rhs.KeyColumnNames - // rhs is the entity persister, it does not handle being referenced through an unique key by a - // collection and always yield its identifier columns as KeyColumnNames. We need to resolve the - // key columns instead. - // 6.0 TODO: consider breaking again that IJoinable.SelectFragment interface for transmitting - // the OuterJoinableAssociation instead of its Joinable property. This would allow to get the - // adequate columns directly instead of re-computing them. - : ((IPropertyMapping) rhs).ToColumns(elementType.RHSUniqueKeyPropertyName); - frag.AddColumns(rhsAlias, elementColumnNames, elementColumnAliases); - AppendIndexColumns(frag, lhsAlias); - AppendIdentifierColumns(frag, lhsAlias); - - return frag.ToSqlStringFragment(false); + return includeCollectionColumns ? GetSelectFragment(lhsAlias, collectionSuffix).ToSqlStringFragment(false) : string.Empty; } /// diff --git a/src/NHibernate/Persister/Collection/OneToManyPersister.cs b/src/NHibernate/Persister/Collection/OneToManyPersister.cs index 2973deec937..2f525fe65f0 100644 --- a/src/NHibernate/Persister/Collection/OneToManyPersister.cs +++ b/src/NHibernate/Persister/Collection/OneToManyPersister.cs @@ -294,7 +294,13 @@ protected override int DoUpdateRows(object id, IPersistentCollection collection, } } + [Obsolete("Please use overload without rhs and rhsAlias parameters")] public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) + { + return SelectFragment(lhsAlias, collectionSuffix, includeCollectionColumns, entityInfo); + } + + public override string SelectFragment(string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { var buf = new StringBuilder(); @@ -317,7 +323,7 @@ public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhs { var selectMode = ReflectHelper.CastOrThrow(ElementPersister, "fetch lazy properties"); if (selectMode != null) - return buf.Append(selectMode.SelectFragment(null, null, lhsAlias, null, false, entityInfo)).ToString(); + return buf.Append(selectMode.SelectFragment(lhsAlias, null, false, entityInfo)).ToString(); } var ojl = (IOuterJoinLoadable)ElementPersister; diff --git a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs index 5e2f3e4748d..205fdb1f212 100644 --- a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs @@ -4461,10 +4461,17 @@ public string SelectFragment( IJoinable rhs, string rhsAlias, string lhsAlias, string entitySuffix, string collectionSuffix, bool includeCollectionColumns, bool includeLazyProperties) { - return SelectFragment(rhs, rhsAlias, lhsAlias, collectionSuffix, includeCollectionColumns, new EntityLoadInfo(entitySuffix) {IncludeLazyProps = includeLazyProperties}); + return SelectFragment(lhsAlias, collectionSuffix, includeCollectionColumns, new EntityLoadInfo(entitySuffix) {IncludeLazyProps = includeLazyProperties}); } + //Since v5.5 + [Obsolete("Please use overload without rhs and rhsAlias parameters")] public string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) + { + return SelectFragment(lhsAlias, collectionSuffix, includeCollectionColumns, entityInfo); + } + + public string SelectFragment(string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { return GetIdentifierSelectFragment(lhsAlias, entityInfo.EntitySuffix).ToSqlStringFragment(false) + GetPropertiesSelectFragment( diff --git a/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs b/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs index bff0512d40f..45ddb1d2123 100644 --- a/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs +++ b/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs @@ -36,6 +36,6 @@ public EntityLoadInfo(string entitySuffix) // 6.0 TODO: merge into 'IJoinable'. internal interface ISupportLazyPropsJoinable { - string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo); + string SelectFragment(string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo); } }