diff --git a/.github/renovate.json b/.github/renovate.json index 32d5a56f64c..689fc32f5d9 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -15,9 +15,7 @@ ], "packageRules": [ { - "matchPackagePrefixes": [ - "NUnit" - ], + "matchSourceUrls": ["https://github.com/nunit/nunit"], "groupName": "NUnit" }, { diff --git a/releasenotes.txt b/releasenotes.txt index 5fd64fc3975..1a7f3e97521 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -1,4 +1,29 @@ -Build 5.4.2 +Build 5.4.3 +============================= + +Release notes - NHibernate - Version 5.4.3 + +11 issues were resolved in this release. + +** Bug + + * #3317 Issue with components list lazy loading with not lazy association + * #3307 IsDirty performance hit since 5.4.0 + * #3295 C# 8/11 Static interface members support + * #3291 Npgsql 6+ issues with null DateTime parameter types + * #3290 Incorrect fetch of Many-to-Many relation + * #3289 Fetching lazy loaded component causes n + 1 query when querying a subclass abstraction + * #3288 NullReferenceException is thrown when using Fetch + +** Task + + * #3349 Release 5.4.3 + * #3348 Merge 5.3.18 in 5.4.x + * #3318 Merge 5.3.17 in 5.4.x + * #3302 Upgrade NUnit3TestAdapter to fix "Unknown framework version 7.0" + + +Build 5.4.2 ============================= Release notes - NHibernate - Version 5.4.2 @@ -234,6 +259,42 @@ Release notes - NHibernate - Version 5.4.0 * #2242 Test case for NH-3972 - SQL error when selecting a column of a subclass when sibling classes have a column of the same name +Build 5.3.18 +============================= + +Release notes - NHibernate - Version 5.3.18 + +3 issues were resolved in this release. + +** Bug + + * #3333 Lazy property with nosetter accessor remains uninitialized + * #3330 Linq with FetchLazyProperties() resets lazy property changes + +** Task + + * #3346 Release 5.3.18 + + +Build 5.3.17 +============================= + +Release notes - NHibernate - Version 5.3.17 + +5 issues were resolved in this release. + +** Bug + + * #3306 Invalid SQL when referencing nullable entity in correlated subquery + * #3304 Fix SetSnapShot CopyTo variance failure + * #3294 Undefined join type failure with cross joins and Informix + +** Task + + * #3315 Release 5.3.17 + * #3300 Backport handling of null DateTime parameters in Npgsql 6+ + + Build 5.3.16 ============================= diff --git a/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj b/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj index ee40ac1072f..95d2047bc2b 100644 --- a/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj +++ b/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj @@ -28,7 +28,7 @@ - + diff --git a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs index 2783bfead0d..6a812cd5888 100644 --- a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs +++ b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs @@ -276,6 +276,45 @@ public async Task TestLinqFetchAllPropertiesAsync() AssertFetchAllProperties(person); } + [TestCase(true)] + [TestCase(false)] + public async Task TestLinqFetchAllProperties_WhenLazyPropertyChangedAsync(bool initLazyPropertyFetchGroup) + { + Person person; + using (var s = OpenSession()) + { + person = await (s.GetAsync(1)); + if (initLazyPropertyFetchGroup) + CollectionAssert.AreEqual(new byte[] { 0 }, person.Image); + + person.Image = new byte[] { 1, 2, 3 }; + + var allPersons = await (s.Query().FetchLazyProperties().ToListAsync()); + // After execute FetchLazyProperties(), I expected to see that the person.Image would be { 1, 2, 3 }. + // Because I changed this person.Image manually, I didn't want to lose those changes. + // But test failed. Оld value returned { 0 }. + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, person.Image); + } + } + + [TestCase(true)] + [TestCase(false)] + public async Task TestLinqFetchProperty_WhenLazyPropertyChangedAsync(bool initLazyPropertyFetchGroup) + { + Person person; + using (var s = OpenSession()) + { + person = await (s.GetAsync(1)); + if (initLazyPropertyFetchGroup) + CollectionAssert.AreEqual(new byte[] { 0 }, person.Image); + + person.Image = new byte[] { 1, 2, 3 }; + + var allPersons = await (s.Query().Fetch(x => x.Image).ToListAsync()); + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, person.Image); + } + } + private static void AssertFetchAllProperties(Person person) { Assert.That(person, Is.Not.Null); diff --git a/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs b/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs index e7714c07a49..f99b79e5420 100644 --- a/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs +++ b/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs @@ -13,10 +13,10 @@ using System.Linq; using NHibernate.Cfg; using NHibernate.Intercept; +using NHibernate.Linq; using NHibernate.Tuple.Entity; using NUnit.Framework; using NUnit.Framework.Constraints; -using NHibernate.Linq; namespace NHibernate.Test.LazyProperty { @@ -67,6 +67,7 @@ protected override void OnSetUp() Id = 1, ALotOfText = "a lot of text ...", Image = new byte[10], + NoSetterImage = new byte[10], FieldInterceptor = "Why not that name?" }); tx.Commit(); @@ -391,5 +392,58 @@ public async Task CanMergeTransientWithLazyPropertyInCollectionAsync() Assert.That(book.Words.First().Content, Is.EqualTo(new byte[1] { 0 })); } } + + [Test(Description = "GH-3333")] + public async Task GetLazyPropertyWithNoSetterAccessor_PropertyShouldBeInitializedAsync() + { + using (ISession s = OpenSession()) + { + var book = await (s.GetAsync(1)); + var image = book.NoSetterImage; + // Fails. Property remains uninitialized after it has been accessed. + Assert.That(NHibernateUtil.IsPropertyInitialized(book, "NoSetterImage"), Is.True); + } + } + + [Test(Description = "GH-3333")] + public async Task GetLazyPropertyWithNoSetterAccessorTwice_ResultsAreSameObjectAsync() + { + using (ISession s = OpenSession()) + { + var book = await (s.GetAsync(1)); + var image = book.NoSetterImage; + var sameImage = book.NoSetterImage; + // Fails. Each call to a property getter returns a new object. + Assert.That(ReferenceEquals(image, sameImage), Is.True); + } + } + + [Test] + public async Task CanSetValueForLazyPropertyNoSetterAsync() + { + Book book; + using (ISession s = OpenSession()) + { + book = await (s.GetAsync(1)); + book.NoSetterImage = new byte[]{10}; + } + + Assert.That(NHibernateUtil.IsPropertyInitialized(book, nameof(book.NoSetterImage)), Is.True); + CollectionAssert.AreEqual(book.NoSetterImage, new byte[] { 10 }); + } + + [Test] + public async Task CanFetchLazyPropertyNoSetterAsync() + { + using (ISession s = OpenSession()) + { + var book = await (s + .Query() + .Fetch(x => x.NoSetterImage) + .FirstAsync(x => x.Id == 1)); + + Assert.That(NHibernateUtil.IsPropertyInitialized(book, nameof(book.NoSetterImage)), Is.True); + } + } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs new file mode 100644 index 00000000000..0a1d6d97655 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs @@ -0,0 +1,58 @@ +//------------------------------------------------------------------------------ +// +// 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.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3288 +{ + using System.Threading.Tasks; + [TestFixture] + public class FetchAndCollectionJoinFixtureAsync : BugTestCase + { + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var middleEntity = new MiddleEntity(); + middleEntity.Components.Add(new Component { MiddleEntity = middleEntity, Value = 1 }); + var te = new TopEntity + { + MiddleEntity = middleEntity + }; + session.Save(middleEntity); + session.Save(te); + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.Delete("from System.Object"); + + transaction.Commit(); + } + + [Test] + public async Task ReuseEntityJoinWithCollectionJoinAsync() + { + using var session = OpenSession(); + + var entities = await (session.Query() + .Fetch(e => e.MiddleEntity) + .Where(e => e.MiddleEntity.Components.Any(e => e.Value != 0)) + .ToListAsync()); + Assert.That(entities.Count, Is.EqualTo(1)); + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3289/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3289/FixtureByCode.cs new file mode 100644 index 00000000000..721d35848ab --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3289/FixtureByCode.cs @@ -0,0 +1,111 @@ +//------------------------------------------------------------------------------ +// +// 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.GH3289 +{ + using System.Threading.Tasks; + [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.Identity)); + rc.Property(x => x.Name); + rc.Component(x => x.Component); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Identity)); + rc.Property(x => x.Name); + rc.Component(x => x.Component); + }); + mapper.JoinedSubclass(rc => + { + rc.Key(k => k.Column("Id")); + rc.Property(x => x.SomeProperty); + }); + mapper.Component(rc => + { + rc.Property(x => x.Field); + rc.Lazy(true); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var e1 = new SubEntity { Name = "Jim" }; + session.Save(e1); + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + if (Dialect.SupportsTemporaryTables) + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + else + session.Delete("from System.Object"); + + transaction.Commit(); + } + + [Test] + public async Task TestSubEntityInterfaceWithFetchIsPropertyInitializedAsync() + { + using var session = OpenSession(); + var data = await (session.Query() + .Fetch(e => e.Component) + .ToListAsync()); + var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component"); + + Assert.That(result, Is.True); + } + + [Test] + public async Task TestEntityInterfaceWithFetchIsPropertyInitializedAsync() + { + using var session = OpenSession(); + var data = await (session.Query() + .Fetch(e => e.Component) + .ToListAsync()); + var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component"); + + Assert.That(result, Is.True); + } + + [Test] + public async Task TestSubEntityWithFetchIsPropertyInitializedAsync() + { + using var session = OpenSession(); + var data = await (session.Query() + .Fetch(e => e.Component) + .ToListAsync()); + var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component"); + + Assert.That(result, Is.True); + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs new file mode 100644 index 00000000000..77229193cbf --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs @@ -0,0 +1,147 @@ +//------------------------------------------------------------------------------ +// +// 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.Collections.Generic; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3290 +{ + using System.Threading.Tasks; + [TestFixture(true)] + [TestFixture(false)] + public class FixtureAsync : TestCaseMappingByCode + { + private readonly bool _detectFetchLoops; + + public FixtureAsync(bool detectFetchLoops) + { + _detectFetchLoops = detectFetchLoops; + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb)); + + rc.Property( + x => x.Name + ); + + rc.Set( + x => x.Children, + v => + { + v.Table("EntityToEntity"); + v.Cascade(Mapping.ByCode.Cascade.None); + v.Inverse(true); + v.Key(x => + { + x.Column("ParentId"); + x.NotNullable(true); + }); + v.Lazy(CollectionLazy.Lazy); + v.Fetch(CollectionFetchMode.Join); + }, + h => h.ManyToMany(m => m.Column("ChildId")) + ); + + rc.Set( + x => x.Parents, + v => + { + v.Table("EntityToEntity"); + v.Cascade(Mapping.ByCode.Cascade.All); + + v.Key(x => + { + x.Column("ChildId"); + x.NotNullable(true); + }); + v.Lazy(CollectionLazy.Lazy); + v.Fetch(CollectionFetchMode.Join); + }, + h => h.ManyToMany(m => m.Column("ParentId")) + ); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.DetectFetchLoops, _detectFetchLoops ? "true" : "false"); + configuration.SetProperty(Environment.MaxFetchDepth, _detectFetchLoops ? "-1" : "2"); + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + var person = new Entity + { + Name = "pers", + Parents = new HashSet() + }; + session.Save(person); + var job = new Entity + { + Name = "job", + Children = new HashSet() + }; + session.Save(job); + + job.Children.Add(person); + person.Parents.Add(job); + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + session.CreateSQLQuery("delete from EntityToEntity").ExecuteUpdate(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public async Task QueryWithFetchAsync() + { + using var session = OpenSession(); + using var _ = session.BeginTransaction(); + + var all = await (session + .QueryOver() + .Fetch(SelectMode.Fetch, x => x.Children) + .Fetch(SelectMode.Fetch, x => x.Parents) + .TransformUsing(Transformers.DistinctRootEntity) + .ListAsync()); + + foreach (var entity in all) + { + var isPerson = entity.Name == "pers"; + if (isPerson) + Assert.That(entity.Parents, Has.Count.EqualTo(1), "Person's job not found or non-unique."); + else + Assert.That(entity.Children, Has.Count.EqualTo(1), "Job's employee not found or non-unique."); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs new file mode 100644 index 00000000000..0d898115b80 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs @@ -0,0 +1,79 @@ +//------------------------------------------------------------------------------ +// +// 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.Linq; +using NHibernate.Criterion; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH3291 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + var e1 = new Person { Name = "Bob", DateOfBirth = new DateTime(2009, 12, 23) }; + session.Save(e1); + + var e2 = new Person { Name = "Sally", DateOfBirth = new DateTime(2018, 9, 30) }; + session.Save(e2); + + 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 LinqAsync() + { + using var session = OpenSession(); + using var _ = session.BeginTransaction(); + + DateTime? dateOfSearch = null; + + var result = await (( + from person in session.Query() + where dateOfSearch == null || person.DateOfBirth > dateOfSearch + select person).ToListAsync()); + + Assert.That(result, Has.Count.EqualTo(2)); + } + + [Test] + public async Task HqlAsync() + { + using var session = OpenSession(); + using var _ = session.BeginTransaction(); + + DateTime? dateOfSearch = null; + + var result = + await (session.CreateQuery("from Person where :DateOfSearch is null OR DateOfBirth > :DateOfSearch") + .SetParameter("DateOfSearch", dateOfSearch, NHibernateUtil.DateTime) + .ListAsync()); + + Assert.That(result, Has.Count.EqualTo(2)); + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs new file mode 100644 index 00000000000..ba446d194f9 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs @@ -0,0 +1,69 @@ +//------------------------------------------------------------------------------ +// +// 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 NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH3306NullableEntityCorrelatedSubquery +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + private const string NAME_JOE = "Joe"; + private const string NAME_ALLEN = "Allen"; + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var joe = new Customer { Name = NAME_JOE }; + session.Save(joe); + + var allen = new Customer { Name = NAME_ALLEN }; + session.Save(allen); + + var joeInvoice0 = new Invoice { Customer = joe, Number = 0 }; + session.Save(joeInvoice0); + + var allenInvoice1 = new Invoice { Customer = allen, Number = 1 }; + session.Save(allenInvoice1); + + tx.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + session.Delete("from Invoice"); + session.Delete("from Customer"); + tx.Commit(); + } + } + + [Test] + public async Task NullableEntityInCorrelatedSubqueryAsync() + { + using (var s = OpenSession()) + { + var customers = s.Query().Where(c => c.Name == NAME_JOE); + var results = await (s.Query() + .Where(i => customers.Any(c => c.Invoices.Any(ci => ci.Customer == i.Customer))).ToListAsync()); + + Assert.That(results.Count, Is.EqualTo(1)); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3317/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3317/FixtureByCode.cs new file mode 100644 index 00000000000..1ca210a186e --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3317/FixtureByCode.cs @@ -0,0 +1,123 @@ +//------------------------------------------------------------------------------ +// +// 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.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3317 +{ + using System.Threading.Tasks; + [TestFixture(true)] + [TestFixture(false)] + public class ComponentsListFixtureAsync : TestCaseMappingByCode + { + private readonly bool _fetchJoinMapping; + private Guid _id; + + public ComponentsListFixtureAsync(bool fetchJoinMapping) + { + _fetchJoinMapping = fetchJoinMapping; + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var tr = session.BeginTransaction(); + var root = new Entity(); + root.Entries.Add(new ComponentListEntry { ComponentReference = null, DummyString = "one", }); + + session.Save(root); + tr.Commit(); + _id = root.Id; + } + + [Test] + public async Task LazyLoadingAsync() + { + using var newSession = OpenSession(); + var reloadedRoot = await (newSession.GetAsync(_id)); + Assert.AreEqual(1, reloadedRoot.Entries.Count); + } + + [Test] + public async Task QueryOverFetchAsync() + { + using var newSession = OpenSession(); + var reloadedRoot = await (newSession.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Entries) + .Where(x => x.Id == _id) + .SingleOrDefaultAsync()); + Assert.AreEqual(1, reloadedRoot.Entries.Count); + } + + [Test] + public async Task LinqFetchAsync() + { + using var newSession = OpenSession(); + var reloadedRoot = await (newSession.Query() + .Fetch(x => x.Entries) + .Where(x => x.Id == _id) + .SingleOrDefaultAsync()); + Assert.AreEqual(1, reloadedRoot.Entries.Count); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateSQLQuery("delete from Entries").ExecuteUpdate(); + session.Delete("from System.Object"); + transaction.Commit(); + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class(rc => + { + rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb)); + rc.Lazy(false); + rc.Property(x => x.Name); + + rc.Bag( + x => x.Entries, + v => + { + if (_fetchJoinMapping) + v.Fetch(CollectionFetchMode.Join); + }, + h => h.Component(cmp => + { + cmp.Property(x => x.DummyString); + cmp.ManyToOne(x => x.ComponentReference); + })); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb)); + rc.Lazy(false); + rc.ManyToOne(x => x.Parent, m => m.NotNullable(true)); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb)); + rc.Lazy(false); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } +} diff --git a/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs index 43d68dce93d..7a89e998c36 100644 --- a/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs +++ b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs @@ -265,6 +265,45 @@ public void TestLinqFetchAllProperties() AssertFetchAllProperties(person); } + [TestCase(true)] + [TestCase(false)] + public void TestLinqFetchAllProperties_WhenLazyPropertyChanged(bool initLazyPropertyFetchGroup) + { + Person person; + using (var s = OpenSession()) + { + person = s.Get(1); + if (initLazyPropertyFetchGroup) + CollectionAssert.AreEqual(new byte[] { 0 }, person.Image); + + person.Image = new byte[] { 1, 2, 3 }; + + var allPersons = s.Query().FetchLazyProperties().ToList(); + // After execute FetchLazyProperties(), I expected to see that the person.Image would be { 1, 2, 3 }. + // Because I changed this person.Image manually, I didn't want to lose those changes. + // But test failed. Оld value returned { 0 }. + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, person.Image); + } + } + + [TestCase(true)] + [TestCase(false)] + public void TestLinqFetchProperty_WhenLazyPropertyChanged(bool initLazyPropertyFetchGroup) + { + Person person; + using (var s = OpenSession()) + { + person = s.Get(1); + if (initLazyPropertyFetchGroup) + CollectionAssert.AreEqual(new byte[] { 0 }, person.Image); + + person.Image = new byte[] { 1, 2, 3 }; + + var allPersons = s.Query().Fetch(x => x.Image).ToList(); + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, person.Image); + } + } + private static void AssertFetchAllProperties(Person person) { Assert.That(person, Is.Not.Null); diff --git a/src/NHibernate.Test/LazyProperty/Book.cs b/src/NHibernate.Test/LazyProperty/Book.cs index 546df8a248b..3dcfe73c567 100644 --- a/src/NHibernate.Test/LazyProperty/Book.cs +++ b/src/NHibernate.Test/LazyProperty/Book.cs @@ -17,6 +17,14 @@ public virtual string ALotOfText public virtual byte[] Image { get; set; } + private byte[] _NoSetterImage; + + public virtual byte[] NoSetterImage + { + get { return _NoSetterImage; } + set { _NoSetterImage = value; } + } + public virtual string FieldInterceptor { get; set; } public virtual IList Words { get; set; } diff --git a/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs b/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs index 5efb7d6e29d..302271f41a3 100644 --- a/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs +++ b/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs @@ -3,6 +3,7 @@ using System.Linq; using NHibernate.Cfg; using NHibernate.Intercept; +using NHibernate.Linq; using NHibernate.Tuple.Entity; using NUnit.Framework; using NUnit.Framework.Constraints; @@ -55,6 +56,7 @@ protected override void OnSetUp() Id = 1, ALotOfText = "a lot of text ...", Image = new byte[10], + NoSetterImage = new byte[10], FieldInterceptor = "Why not that name?" }); tx.Commit(); @@ -385,5 +387,58 @@ public void CanMergeTransientWithLazyPropertyInCollection() Assert.That(book.Words.First().Content, Is.EqualTo(new byte[1] { 0 })); } } + + [Test(Description = "GH-3333")] + public void GetLazyPropertyWithNoSetterAccessor_PropertyShouldBeInitialized() + { + using (ISession s = OpenSession()) + { + var book = s.Get(1); + var image = book.NoSetterImage; + // Fails. Property remains uninitialized after it has been accessed. + Assert.That(NHibernateUtil.IsPropertyInitialized(book, "NoSetterImage"), Is.True); + } + } + + [Test(Description = "GH-3333")] + public void GetLazyPropertyWithNoSetterAccessorTwice_ResultsAreSameObject() + { + using (ISession s = OpenSession()) + { + var book = s.Get(1); + var image = book.NoSetterImage; + var sameImage = book.NoSetterImage; + // Fails. Each call to a property getter returns a new object. + Assert.That(ReferenceEquals(image, sameImage), Is.True); + } + } + + [Test] + public void CanSetValueForLazyPropertyNoSetter() + { + Book book; + using (ISession s = OpenSession()) + { + book = s.Get(1); + book.NoSetterImage = new byte[]{10}; + } + + Assert.That(NHibernateUtil.IsPropertyInitialized(book, nameof(book.NoSetterImage)), Is.True); + CollectionAssert.AreEqual(book.NoSetterImage, new byte[] { 10 }); + } + + [Test] + public void CanFetchLazyPropertyNoSetter() + { + using (ISession s = OpenSession()) + { + var book = s + .Query() + .Fetch(x => x.NoSetterImage) + .First(x => x.Id == 1); + + Assert.That(NHibernateUtil.IsPropertyInitialized(book, nameof(book.NoSetterImage)), Is.True); + } + } } } diff --git a/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml b/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml index 91189c36e30..533576580cd 100644 --- a/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml +++ b/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml @@ -11,6 +11,7 @@ + diff --git a/src/NHibernate.Test/NHSpecificTest/GH3288/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3288/Entity.cs new file mode 100644 index 00000000000..9f85c760a14 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3288/Entity.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH3288 +{ + class TopEntity + { + public virtual int Id { get; set; } + public virtual MiddleEntity MiddleEntity { get; set; } + } + class MiddleEntity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual ISet Components { get; set; } = new HashSet(); + } + + class Component + { + public virtual MiddleEntity MiddleEntity { get; set; } + public virtual int Value { get; set; } + + public override bool Equals(object obj) + { + return (obj as Component)?.MiddleEntity.Id == MiddleEntity.Id; + } + + public override int GetHashCode() + { + return MiddleEntity.Id.GetHashCode(); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs new file mode 100644 index 00000000000..440c4f772bd --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs @@ -0,0 +1,47 @@ +using System.Linq; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3288 +{ + [TestFixture] + public class FetchAndCollectionJoinFixture : BugTestCase + { + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var middleEntity = new MiddleEntity(); + middleEntity.Components.Add(new Component { MiddleEntity = middleEntity, Value = 1 }); + var te = new TopEntity + { + MiddleEntity = middleEntity + }; + session.Save(middleEntity); + session.Save(te); + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.Delete("from System.Object"); + + transaction.Commit(); + } + + [Test] + public void ReuseEntityJoinWithCollectionJoin() + { + using var session = OpenSession(); + + var entities = session.Query() + .Fetch(e => e.MiddleEntity) + .Where(e => e.MiddleEntity.Components.Any(e => e.Value != 0)) + .ToList(); + Assert.That(entities.Count, Is.EqualTo(1)); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3288/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3288/Mappings.hbm.xml new file mode 100644 index 00000000000..b9cd68ded6f --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3288/Mappings.hbm.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH3289/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH3289/FixtureByCode.cs new file mode 100644 index 00000000000..8bc20205c12 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3289/FixtureByCode.cs @@ -0,0 +1,100 @@ +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3289 +{ + [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.Identity)); + rc.Property(x => x.Name); + rc.Component(x => x.Component); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Identity)); + rc.Property(x => x.Name); + rc.Component(x => x.Component); + }); + mapper.JoinedSubclass(rc => + { + rc.Key(k => k.Column("Id")); + rc.Property(x => x.SomeProperty); + }); + mapper.Component(rc => + { + rc.Property(x => x.Field); + rc.Lazy(true); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var e1 = new SubEntity { Name = "Jim" }; + session.Save(e1); + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + if (Dialect.SupportsTemporaryTables) + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + else + session.Delete("from System.Object"); + + transaction.Commit(); + } + + [Test] + public void TestSubEntityInterfaceWithFetchIsPropertyInitialized() + { + using var session = OpenSession(); + var data = session.Query() + .Fetch(e => e.Component) + .ToList(); + var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component"); + + Assert.That(result, Is.True); + } + + [Test] + public void TestEntityInterfaceWithFetchIsPropertyInitialized() + { + using var session = OpenSession(); + var data = session.Query() + .Fetch(e => e.Component) + .ToList(); + var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component"); + + Assert.That(result, Is.True); + } + + [Test] + public void TestSubEntityWithFetchIsPropertyInitialized() + { + using var session = OpenSession(); + var data = session.Query() + .Fetch(e => e.Component) + .ToList(); + var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component"); + + Assert.That(result, Is.True); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3289/Models.cs b/src/NHibernate.Test/NHSpecificTest/GH3289/Models.cs new file mode 100644 index 00000000000..ffbbeeb7bf7 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3289/Models.cs @@ -0,0 +1,37 @@ +namespace NHibernate.Test.NHSpecificTest.GH3289 +{ + public interface IEntity + { + int Id { get; set; } + string Name { get; set; } + Component Component { get; set; } + } + + public interface ISubEntity : IEntity + { + public bool SomeProperty { get; set; } + } + + public class Entity : IEntity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual Component Component { get; set; } + } + public class OtherEntity : IEntity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual Component Component { get; set; } + } + + public class SubEntity : Entity, ISubEntity + { + public virtual bool SomeProperty { get; set; } + } + + public class Component + { + public virtual string Field { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3290/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3290/Entity.cs new file mode 100644 index 00000000000..0ef176af019 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3290/Entity.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH3290 +{ + class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual ISet Parents { get; set; } + public virtual ISet Children { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs new file mode 100644 index 00000000000..f7c6fe8dbe0 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs @@ -0,0 +1,136 @@ +using System.Collections.Generic; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3290 +{ + [TestFixture(true)] + [TestFixture(false)] + public class Fixture : TestCaseMappingByCode + { + private readonly bool _detectFetchLoops; + + public Fixture(bool detectFetchLoops) + { + _detectFetchLoops = detectFetchLoops; + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb)); + + rc.Property( + x => x.Name + ); + + rc.Set( + x => x.Children, + v => + { + v.Table("EntityToEntity"); + v.Cascade(Mapping.ByCode.Cascade.None); + v.Inverse(true); + v.Key(x => + { + x.Column("ParentId"); + x.NotNullable(true); + }); + v.Lazy(CollectionLazy.Lazy); + v.Fetch(CollectionFetchMode.Join); + }, + h => h.ManyToMany(m => m.Column("ChildId")) + ); + + rc.Set( + x => x.Parents, + v => + { + v.Table("EntityToEntity"); + v.Cascade(Mapping.ByCode.Cascade.All); + + v.Key(x => + { + x.Column("ChildId"); + x.NotNullable(true); + }); + v.Lazy(CollectionLazy.Lazy); + v.Fetch(CollectionFetchMode.Join); + }, + h => h.ManyToMany(m => m.Column("ParentId")) + ); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.DetectFetchLoops, _detectFetchLoops ? "true" : "false"); + configuration.SetProperty(Environment.MaxFetchDepth, _detectFetchLoops ? "-1" : "2"); + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + var person = new Entity + { + Name = "pers", + Parents = new HashSet() + }; + session.Save(person); + var job = new Entity + { + Name = "job", + Children = new HashSet() + }; + session.Save(job); + + job.Children.Add(person); + person.Parents.Add(job); + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + session.CreateSQLQuery("delete from EntityToEntity").ExecuteUpdate(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public void QueryWithFetch() + { + using var session = OpenSession(); + using var _ = session.BeginTransaction(); + + var all = session + .QueryOver() + .Fetch(SelectMode.Fetch, x => x.Children) + .Fetch(SelectMode.Fetch, x => x.Parents) + .TransformUsing(Transformers.DistinctRootEntity) + .List(); + + foreach (var entity in all) + { + var isPerson = entity.Name == "pers"; + if (isPerson) + Assert.That(entity.Parents, Has.Count.EqualTo(1), "Person's job not found or non-unique."); + else + Assert.That(entity.Children, Has.Count.EqualTo(1), "Job's employee not found or non-unique."); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs new file mode 100644 index 00000000000..fa01d89b8f9 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using NHibernate.Criterion; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3291 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + var e1 = new Person { Name = "Bob", DateOfBirth = new DateTime(2009, 12, 23) }; + session.Save(e1); + + var e2 = new Person { Name = "Sally", DateOfBirth = new DateTime(2018, 9, 30) }; + session.Save(e2); + + 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 Linq() + { + using var session = OpenSession(); + using var _ = session.BeginTransaction(); + + DateTime? dateOfSearch = null; + + var result = ( + from person in session.Query() + where dateOfSearch == null || person.DateOfBirth > dateOfSearch + select person).ToList(); + + Assert.That(result, Has.Count.EqualTo(2)); + } + + [Test] + public void Hql() + { + using var session = OpenSession(); + using var _ = session.BeginTransaction(); + + DateTime? dateOfSearch = null; + + var result = + session.CreateQuery("from Person where :DateOfSearch is null OR DateOfBirth > :DateOfSearch") + .SetParameter("DateOfSearch", dateOfSearch, NHibernateUtil.DateTime) + .List(); + + Assert.That(result, Has.Count.EqualTo(2)); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3291/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3291/Mappings.hbm.xml new file mode 100644 index 00000000000..1088c98b593 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3291/Mappings.hbm.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH3291/Person.cs b/src/NHibernate.Test/NHSpecificTest/GH3291/Person.cs new file mode 100644 index 00000000000..d80efc2e094 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3291/Person.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH3291 +{ + class Person + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual DateTime? DateOfBirth { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs new file mode 100644 index 00000000000..d1d511eca59 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs @@ -0,0 +1,57 @@ +using System.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3306NullableEntityCorrelatedSubquery +{ + [TestFixture] + public class Fixture : BugTestCase + { + private const string NAME_JOE = "Joe"; + private const string NAME_ALLEN = "Allen"; + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var joe = new Customer { Name = NAME_JOE }; + session.Save(joe); + + var allen = new Customer { Name = NAME_ALLEN }; + session.Save(allen); + + var joeInvoice0 = new Invoice { Customer = joe, Number = 0 }; + session.Save(joeInvoice0); + + var allenInvoice1 = new Invoice { Customer = allen, Number = 1 }; + session.Save(allenInvoice1); + + tx.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + session.Delete("from Invoice"); + session.Delete("from Customer"); + tx.Commit(); + } + } + + [Test] + public void NullableEntityInCorrelatedSubquery() + { + using (var s = OpenSession()) + { + var customers = s.Query().Where(c => c.Name == NAME_JOE); + var results = s.Query() + .Where(i => customers.Any(c => c.Invoices.Any(ci => ci.Customer == i.Customer))).ToList(); + + Assert.That(results.Count, Is.EqualTo(1)); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Mappings.hbm.xml new file mode 100644 index 00000000000..515584fe116 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Mappings.hbm.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Model.cs b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Model.cs new file mode 100644 index 00000000000..2c472579b00 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Model.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH3306NullableEntityCorrelatedSubquery +{ + public class Customer + { + public virtual int ID { get; protected set; } + public virtual ISet Invoices { get; set; } + public virtual string Name { get; set; } + } + + public class Invoice + { + public virtual int ID { get; protected set; } + public virtual Customer Customer { get; set; } + public virtual int Number { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3317/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3317/Entity.cs new file mode 100644 index 00000000000..1e43d5554c1 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3317/Entity.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH3317 +{ + public class Entity + { + public Guid Id { get; set; } + public string Name { get; set; } + public IList Entries { get; set; } = new List(); + } + + public class ComponentListEntry + { + public string DummyString { get; set; } + public EntityWithParent ComponentReference { get; set; } + } + + public class EntityWithParent + { + public Guid Id { get; set; } + public ParentEntity Parent { get; set; } + } + + public class ParentEntity + { + public Guid Id { get; set; } + public string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3317/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH3317/FixtureByCode.cs new file mode 100644 index 00000000000..3847af79449 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3317/FixtureByCode.cs @@ -0,0 +1,112 @@ +using System; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3317 +{ + [TestFixture(true)] + [TestFixture(false)] + public class ComponentsListFixture : TestCaseMappingByCode + { + private readonly bool _fetchJoinMapping; + private Guid _id; + + public ComponentsListFixture(bool fetchJoinMapping) + { + _fetchJoinMapping = fetchJoinMapping; + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var tr = session.BeginTransaction(); + var root = new Entity(); + root.Entries.Add(new ComponentListEntry { ComponentReference = null, DummyString = "one", }); + + session.Save(root); + tr.Commit(); + _id = root.Id; + } + + [Test] + public void LazyLoading() + { + using var newSession = OpenSession(); + var reloadedRoot = newSession.Get(_id); + Assert.AreEqual(1, reloadedRoot.Entries.Count); + } + + [Test] + public void QueryOverFetch() + { + using var newSession = OpenSession(); + var reloadedRoot = newSession.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Entries) + .Where(x => x.Id == _id) + .SingleOrDefault(); + Assert.AreEqual(1, reloadedRoot.Entries.Count); + } + + [Test] + public void LinqFetch() + { + using var newSession = OpenSession(); + var reloadedRoot = newSession.Query() + .Fetch(x => x.Entries) + .Where(x => x.Id == _id) + .SingleOrDefault(); + Assert.AreEqual(1, reloadedRoot.Entries.Count); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateSQLQuery("delete from Entries").ExecuteUpdate(); + session.Delete("from System.Object"); + transaction.Commit(); + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class(rc => + { + rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb)); + rc.Lazy(false); + rc.Property(x => x.Name); + + rc.Bag( + x => x.Entries, + v => + { + if (_fetchJoinMapping) + v.Fetch(CollectionFetchMode.Join); + }, + h => h.Component(cmp => + { + cmp.Property(x => x.DummyString); + cmp.ManyToOne(x => x.ComponentReference); + })); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb)); + rc.Lazy(false); + rc.ManyToOne(x => x.Parent, m => m.NotNullable(true)); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb)); + rc.Lazy(false); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } +} diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index 1076bd8b1cd..95f20cc3e32 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -63,7 +63,7 @@ - + diff --git a/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs b/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs index 1f18e170e8a..722bb604af2 100644 --- a/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs +++ b/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs @@ -816,6 +816,50 @@ private static BinaryFormatter GetFormatter() #endif } +#if NETCOREAPP3_1_OR_GREATER + public interface IWithStaticMethods + { + // C# 8 + static void StaticMethod() + { + } + +#if NET7_0_OR_GREATER + // C# 11 + static abstract void StaticAbstractMethod(); + + // C# 11 + static virtual void StaticVirtualMethod() + { + } +#endif + } + + public class ClassWithStaticInterfaceMethods : IWithStaticMethods + { + public static void StaticAbstractMethod() + { + } + } + + [Test(Description = "GH3295")] + public void VerifyProxyForClassWithStaticInterfaceMethod() + { + var factory = new StaticProxyFactory(); + factory.PostInstantiate( + typeof(ClassWithStaticInterfaceMethods).FullName, + typeof(ClassWithStaticInterfaceMethods), + new HashSet { typeof(INHibernateProxy) }, + null, null, null, true); + + var proxy = factory.GetProxy(1, null); + Assert.That(proxy, Is.Not.Null); + Assert.That(proxy, Is.InstanceOf()); + + Assert.That(factory.GetFieldInterceptionProxy(), Is.InstanceOf()); + } +#endif + #if NETFX private static void VerifyGeneratedAssembly(System.Action assemblyGenerator) { diff --git a/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs b/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs index e00af722111..47b03b50ec9 100644 --- a/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs +++ b/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections; +using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using NHibernate.Collection.Generic.SetHelpers; +using NSubstitute.ExceptionExtensions; using NUnit.Framework; namespace NHibernate.Test.UtilityTest @@ -70,6 +73,29 @@ public void TestCopyTo() Assert.That(list, Is.EquivalentTo(array)); } + [Test] + public void TestCopyToObjectArray() + { + var list = new List { "test1", null, "test2" }; + ICollection sn = new SetSnapShot(list); + + var array = new object[3]; + sn.CopyTo(array, 0); + Assert.That(list, Is.EquivalentTo(array)); + } + + [Test] + public void WhenCopyToIsCalledWithIncompatibleArrayTypeThenThrowArgumentOrInvalidCastException() + { + var list = new List { "test1", null, "test2" }; + ICollection sn = new SetSnapShot(list); + + var array = new int[3]; + Assert.That( + () => sn.CopyTo(array, 0), + Throws.ArgumentException.Or.TypeOf()); + } + [Test] public void TestSerialization() { diff --git a/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj b/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj index 7acfa11bfc3..12c33643f9a 100644 --- a/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj +++ b/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs b/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs index 5dd5fc8fe22..deea09fb3a5 100644 --- a/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs +++ b/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs @@ -182,11 +182,7 @@ private static void SetupNpgsql(Cfg.Configuration cfg) using (var cmd = conn.CreateCommand()) { - cmd.CommandText = - @"CREATE OR REPLACE FUNCTION uuid_generate_v4() - RETURNS uuid - AS '$libdir/uuid-ossp', 'uuid_generate_v4' - VOLATILE STRICT LANGUAGE C;"; + cmd.CommandText = "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";"; cmd.ExecuteNonQuery(); } diff --git a/src/NHibernate/Async/Event/Default/AbstractFlushingEventListener.cs b/src/NHibernate/Async/Event/Default/AbstractFlushingEventListener.cs index dcea3f46739..ae648f8a595 100644 --- a/src/NHibernate/Async/Event/Default/AbstractFlushingEventListener.cs +++ b/src/NHibernate/Async/Event/Default/AbstractFlushingEventListener.cs @@ -191,6 +191,7 @@ protected virtual async Task PrepareEntityFlushesAsync(IEventSource session, Can cancellationToken.ThrowIfCancellationRequested(); log.Debug("processing flush-time cascades"); + var anything = Anything; ICollection list = IdentityMap.ConcurrentEntries(session.PersistenceContext.EntityEntries); //safe from concurrent modification because of how entryList() is implemented on IdentityMap foreach (DictionaryEntry me in list) @@ -199,7 +200,7 @@ protected virtual async Task PrepareEntityFlushesAsync(IEventSource session, Can Status status = entry.Status; if (status == Status.Loaded || status == Status.Saving || status == Status.ReadOnly) { - await (CascadeOnFlushAsync(session, entry.Persister, me.Key, Anything, cancellationToken)).ConfigureAwait(false); + await (CascadeOnFlushAsync(session, entry.Persister, me.Key, anything, cancellationToken)).ConfigureAwait(false); } } } diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index a7153ea131b..f797434cbe4 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -802,8 +802,13 @@ private async Task UpdateLazyPropertiesFromResultSetAsync(DbDataReader rs, int i ? persister.EntityMetamodel.BytecodeEnhancementMetadata.GetUninitializedLazyProperties(entry.LoadedState) : persister.EntityMetamodel.BytecodeEnhancementMetadata.GetUninitializedLazyProperties(obj); - var updateLazyProperties = fetchLazyProperties?.Intersect(uninitializedProperties).ToArray(); - if (updateLazyProperties?.Length == 0) + if (uninitializedProperties.Count == 0) + return; + + var updateLazyProperties = fetchAllProperties + ? uninitializedProperties.ToArray() + : fetchLazyProperties.Intersect(uninitializedProperties).ToArray(); + if (updateLazyProperties.Length == 0) { return; // No new lazy properites were loaded } @@ -819,7 +824,7 @@ private async Task UpdateLazyPropertiesFromResultSetAsync(DbDataReader rs, int i ? EntityAliases[i].SuffixedPropertyAliases : GetSubclassEntityAliases(i, persister); - if (!await (persister.InitializeLazyPropertiesAsync(rs, id, obj, cols, updateLazyProperties, fetchAllProperties, session, cancellationToken)).ConfigureAwait(false)) + if (!await (persister.InitializeLazyPropertiesAsync(rs, id, obj, cols, updateLazyProperties, false, session, cancellationToken)).ConfigureAwait(false)) { return; } diff --git a/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs b/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs index a8985c46c03..475bebf6c2e 100644 --- a/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs +++ b/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs @@ -114,12 +114,16 @@ IEnumerator IEnumerable.GetEnumerator() void ICollection.CopyTo(Array array, int index) { - if (!(array is T[] typedArray)) + if (array is T[] typedArray) { - throw new ArgumentException($"Array must be of type {typeof(T[])}.", nameof(array)); + CopyTo(typedArray, index); + return; } - CopyTo(typedArray, index); + if (_hasNull) + array.SetValue(default(T), index); + ICollection keysCollection = _values.Keys; + keysCollection.CopyTo(array, index + (_hasNull ? 1 : 0)); } public int Count => _values.Count + (_hasNull ? 1 : 0); @@ -153,12 +157,26 @@ protected SetSnapShot(SerializationInfo info, StreamingContext context) : base(i void ICollection.CopyTo(Array array, int index) { - if (!(array is T[] typedArray)) + if (array is T[] typedArray) { - throw new ArgumentException($"Array must be of type {typeof(T[])}.", nameof(array)); + CopyTo(typedArray, index); + return; } - CopyTo(typedArray, index); + if (array == null) + throw new ArgumentNullException(nameof(array)); + + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), index, "Array index cannot be negative"); + + if (index > array.Length || Count > array.Length - index) + throw new ArgumentException("Provided array is too small", nameof(array)); + + foreach (var value in this) + { + array.SetValue(value, index); + index++; + } } bool ICollection.IsSynchronized => false; diff --git a/src/NHibernate/Dialect/InformixDialect.cs b/src/NHibernate/Dialect/InformixDialect.cs index 2f942815a57..4be77acd721 100644 --- a/src/NHibernate/Dialect/InformixDialect.cs +++ b/src/NHibernate/Dialect/InformixDialect.cs @@ -333,6 +333,9 @@ public override JoinFragment CreateOuterJoinFragment() return new InformixJoinFragment(); } + /// + public override bool SupportsCrossJoin => false; + /// The SQL literal value to which this database maps boolean values. /// The boolean value /// The appropriate SQL literal. diff --git a/src/NHibernate/Dialect/InformixDialect0940.cs b/src/NHibernate/Dialect/InformixDialect0940.cs index bdfcae4f27f..13563df43e8 100644 --- a/src/NHibernate/Dialect/InformixDialect0940.cs +++ b/src/NHibernate/Dialect/InformixDialect0940.cs @@ -126,10 +126,7 @@ public override JoinFragment CreateOuterJoinFragment() return new ANSIJoinFragment(); } - /// - public override bool SupportsCrossJoin => false; - - /// + /// /// Does this Dialect have some kind of LIMIT syntax? /// /// False, unless overridden. diff --git a/src/NHibernate/Driver/NpgsqlDriver.cs b/src/NHibernate/Driver/NpgsqlDriver.cs index c638e256221..9a4a4b06771 100644 --- a/src/NHibernate/Driver/NpgsqlDriver.cs +++ b/src/NHibernate/Driver/NpgsqlDriver.cs @@ -1,3 +1,4 @@ +using System; using System.Data; using System.Data.Common; using NHibernate.AdoNet; @@ -74,14 +75,37 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq // Since the .NET currency type has 4 decimal places, we use a decimal type in PostgreSQL instead of its native 2 decimal currency type. dbParam.DbType = DbType.Decimal; } - else if (DriverVersionMajor < 6 || sqlType.DbType != DbType.DateTime) + else { dbParam.DbType = sqlType.DbType; } - else + } + + public override void AdjustCommand(DbCommand command) + { + if (DriverVersionMajor >= 6) { - // Let Npgsql 6 driver to decide parameter type + for (var i = 0; i < command.Parameters.Count; i++) + { + var parameter = command.Parameters[i]; + if (parameter.DbType == DbType.DateTime && + parameter.Value is DateTime dateTime && + dateTime.Kind != DateTimeKind.Utc) + { + // There are breaking changes in Npgsql 6 as following: + // UTC DateTime is now strictly mapped to timestamptz, + // while Local/Unspecified DateTime is now strictly mapped to timestamp. + // + // DbType.DateTime now maps to timestamptz, not timestamp. + // DbType.DateTime2 continues to map to timestamp + // + // See more details here: https://www.npgsql.org/doc/release-notes/6.0.html#detailed-notes + parameter.DbType = DbType.DateTime2; + } + } } + + base.AdjustCommand(command); } // Prior to v3, Npgsql was expecting DateTime for time. diff --git a/src/NHibernate/Event/Default/AbstractFlushingEventListener.cs b/src/NHibernate/Event/Default/AbstractFlushingEventListener.cs index f9f968a4b2a..979ea774c8c 100644 --- a/src/NHibernate/Event/Default/AbstractFlushingEventListener.cs +++ b/src/NHibernate/Event/Default/AbstractFlushingEventListener.cs @@ -188,6 +188,7 @@ protected virtual void PrepareEntityFlushes(IEventSource session) { log.Debug("processing flush-time cascades"); + var anything = Anything; ICollection list = IdentityMap.ConcurrentEntries(session.PersistenceContext.EntityEntries); //safe from concurrent modification because of how entryList() is implemented on IdentityMap foreach (DictionaryEntry me in list) @@ -196,7 +197,7 @@ protected virtual void PrepareEntityFlushes(IEventSource session) Status status = entry.Status; if (status == Status.Loaded || status == Status.Saving || status == Status.ReadOnly) { - CascadeOnFlush(session, entry.Persister, me.Key, Anything); + CascadeOnFlush(session, entry.Persister, me.Key, anything); } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs index 7cc0bcae9c6..ac22725e582 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs @@ -511,7 +511,7 @@ internal string GetEntitySuffix(FromElement fromElement) internal string GetCollectionSuffix(FromElement fromElement) { - if (!fromElement.CollectionJoin && fromElement.QueryableCollection == null) + if (fromElement.QueryableCollection == null) { return null; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs index f0ac747bf4d..4b0f127a2ca 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -398,7 +398,7 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string bool joinIsNeeded; //For nullable entity comparisons we always need to add join (like not constrained one-to-one or not-found ignore associations) - bool comparisonWithNullableEntity = entityType.IsNullable && Walker.IsComparativeExpressionClause; + bool comparisonWithNullableEntity = entityType.IsNullable && Walker.IsComparativeExpressionClause && !IsCorrelatedSubselect; if ( IsDotNode( parent ) ) { diff --git a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs index a042af3c12d..31401f7df81 100644 --- a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs +++ b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs @@ -52,6 +52,16 @@ private void Process( { var metadata = queryModelVisitor.VisitorParameters.SessionFactory .GetClassMetadata(resultOperator.RelationMember.ReflectedType); + if (metadata == null) + { + var entityName = queryModelVisitor.VisitorParameters.SessionFactory.GetImplementors( + resultOperator.RelationMember.ReflectedType.FullName).FirstOrDefault(); + if (!string.IsNullOrEmpty(entityName)) + { + metadata = queryModelVisitor.VisitorParameters.SessionFactory.GetClassMetadata(entityName); + } + } + propType = metadata?.GetPropertyType(resultOperator.RelationMember.Name); } diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 731ee2cc670..e2650582718 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -192,7 +192,15 @@ private void AddAssociationToJoinTree(IAssociationType type, string[] aliasedLhs if (qc != null) { - _joinQueue.Enqueue(new CollectionJoinQueueEntry(qc, subalias, path, pathAlias)); + var collection = new CollectionJoinQueueEntry(qc, subalias, path, pathAlias); + // Many-to-Many element entity join needs to be added right after collection bridge table + // (see IsManyToManyWith, ManyToManySelectFragment, IsManyToManyRoot usages) + if (qc.IsManyToMany) + { + collection.Walk(this); + return; + } + _joinQueue.Enqueue(collection); } else if (joinable is IOuterJoinLoadable jl) { @@ -367,6 +375,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st } else if (type.IsComponentType) { + _joinQueue.Enqueue(NextLevelJoinQueueEntry.Instance); WalkCompositeElementTree( (IAbstractComponentType) type, persister.ElementColumnNames, diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index 53294db26de..ad3b5109021 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -1208,8 +1208,13 @@ private void UpdateLazyPropertiesFromResultSet(DbDataReader rs, int i, object ob ? persister.EntityMetamodel.BytecodeEnhancementMetadata.GetUninitializedLazyProperties(entry.LoadedState) : persister.EntityMetamodel.BytecodeEnhancementMetadata.GetUninitializedLazyProperties(obj); - var updateLazyProperties = fetchLazyProperties?.Intersect(uninitializedProperties).ToArray(); - if (updateLazyProperties?.Length == 0) + if (uninitializedProperties.Count == 0) + return; + + var updateLazyProperties = fetchAllProperties + ? uninitializedProperties.ToArray() + : fetchLazyProperties.Intersect(uninitializedProperties).ToArray(); + if (updateLazyProperties.Length == 0) { return; // No new lazy properites were loaded } @@ -1225,7 +1230,7 @@ private void UpdateLazyPropertiesFromResultSet(DbDataReader rs, int i, object ob ? EntityAliases[i].SuffixedPropertyAliases : GetSubclassEntityAliases(i, persister); - if (!persister.InitializeLazyProperties(rs, id, obj, cols, updateLazyProperties, fetchAllProperties, session)) + if (!persister.InitializeLazyProperties(rs, id, obj, cols, updateLazyProperties, false, session)) { return; } diff --git a/src/NHibernate/Proxy/ProxyBuilderHelper.cs b/src/NHibernate/Proxy/ProxyBuilderHelper.cs index 64ee3879e67..7f922911616 100644 --- a/src/NHibernate/Proxy/ProxyBuilderHelper.cs +++ b/src/NHibernate/Proxy/ProxyBuilderHelper.cs @@ -21,6 +21,8 @@ namespace NHibernate.Proxy { internal static class ProxyBuilderHelper { + private const BindingFlags ProxiableMethodsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + private static readonly ConstructorInfo ObjectConstructor = typeof(object).GetConstructor(System.Type.EmptyTypes); private static readonly ConstructorInfo SecurityCriticalAttributeConstructor = typeof(SecurityCriticalAttribute).GetConstructor(System.Type.EmptyTypes); private static readonly ConstructorInfo IgnoresAccessChecksToAttributeConstructor = typeof(IgnoresAccessChecksToAttribute).GetConstructor(new[] {typeof(string)}); @@ -94,10 +96,7 @@ internal static void CallDefaultBaseConstructor(ILGenerator il, System.Type pare internal static IEnumerable GetProxiableMethods(System.Type type) { - const BindingFlags candidateMethodsBindingFlags = - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - - return type.GetMethods(candidateMethodsBindingFlags).Where(m => m.IsProxiable()); + return type.GetMethods(ProxiableMethodsBindingFlags).Where(m => m.IsProxiable()); } internal static IEnumerable GetProxiableMethods(System.Type type, IEnumerable interfaces) @@ -105,12 +104,12 @@ internal static IEnumerable GetProxiableMethods(System.Type type, IE if (type.IsInterface || type == typeof(object) || type.GetInterfaces().Length == 0) { return GetProxiableMethods(type) - .Concat(interfaces.SelectMany(i => i.GetMethods())) + .Concat(interfaces.SelectMany(i => i.GetMethods(ProxiableMethodsBindingFlags))) .Distinct(); } var proxiableMethods = new HashSet(GetProxiableMethods(type), new MethodInfoComparer(type)); - foreach (var interfaceMethod in interfaces.SelectMany(i => i.GetMethods())) + foreach (var interfaceMethod in interfaces.SelectMany(i => i.GetMethods(ProxiableMethodsBindingFlags))) { proxiableMethods.Add(interfaceMethod); } diff --git a/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs b/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs index 3913d678032..2a79d56ca74 100644 --- a/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs +++ b/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs @@ -12,6 +12,7 @@ using NHibernate.Util; using System.Runtime.Serialization; using NHibernate.Bytecode.Lightweight; +using NHibernate.Intercept; namespace NHibernate.Tuple.Entity { @@ -306,6 +307,16 @@ public override bool IsLifecycleImplementor public override void SetPropertyValue(object entity, int i, object value) { + // If there is no property setter we need to manually intercept value for proper lazy property handling. + if (IsInstrumented && setters[i].PropertyName == null) + { + IFieldInterceptor interceptor = _enhancementMetadata.ExtractInterceptor(entity); + if (interceptor != null) + { + value = interceptor.Intercept(entity, EntityMetamodel.PropertyNames[i], value, true); + } + } + if (isBytecodeProviderImpl && optimizer?.AccessOptimizer != null) { optimizer.AccessOptimizer.SetPropertyValue(entity, i, value);