From 47aaa7eec72e587c24e2bd3f26a09e4385ff30c4 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 30 Jun 2023 11:27:21 +0300 Subject: [PATCH 1/4] Fix FetchLazyProperties updates modified properties (#3343) Fixes #3330 Co-authored-by: Alexander Shutov --- .../FetchLazyPropertiesFixture.cs | 39 +++++++++++++++++++ .../FetchLazyPropertiesFixture.cs | 39 +++++++++++++++++++ src/NHibernate/Async/Loader/Loader.cs | 11 ++++-- src/NHibernate/Loader/Loader.cs | 11 ++++-- 4 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs index e95e9c6fb3b..50a24601b86 100644 --- a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs +++ b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs @@ -274,6 +274,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/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs index f1a81fb7e61..9c3f00a4bcd 100644 --- a/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs +++ b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs @@ -263,6 +263,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/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index a8a235d48c3..7ee01139d3a 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -797,8 +797,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 } @@ -814,7 +819,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/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index ba56146dbbd..473cd620635 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -1203,8 +1203,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 } @@ -1220,7 +1225,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; } From 806745178c242b19500ec67e9ee6ee8a73f1a0c3 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 30 Jun 2023 11:31:39 +0300 Subject: [PATCH 2/4] Enable dev builds for 5.3.18 --- build-common/NHibernate.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-common/NHibernate.props b/build-common/NHibernate.props index 714717ed58a..84b900b1f9f 100644 --- a/build-common/NHibernate.props +++ b/build-common/NHibernate.props @@ -3,9 +3,9 @@ 5.3 - 17 + 18 - + dev $(NhVersion).$(VersionPatch) $(VersionSuffix).$(BuildNumber) From 9a8ac7784f5d42432ca91b25edd4f900adbbf038 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sat, 1 Jul 2023 12:35:17 +0300 Subject: [PATCH 3/4] Fix lazy property handling with field accessors (#3345) Fixes #3333 Co-authored-by: sashakboo <31510839+sashakboo@users.noreply.github.com> --- .../Async/LazyProperty/LazyPropertyFixture.cs | 56 ++++++++++++++++++- src/NHibernate.Test/LazyProperty/Book.cs | 8 +++ .../LazyProperty/LazyPropertyFixture.cs | 55 ++++++++++++++++++ .../LazyProperty/Mappings.hbm.xml | 1 + .../Tuple/Entity/PocoEntityTuplizer.cs | 11 ++++ 5 files changed, 130 insertions(+), 1 deletion(-) diff --git a/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs b/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs index e0252e2647b..0fc6739dd78 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 { @@ -69,6 +69,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(); @@ -393,5 +394,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/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 8a2b2d6939a..d49f22c2f4e 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; @@ -57,6 +58,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(); @@ -387,5 +389,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/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); From 218379c7535fb8da3ab2a530a46ad537f5fecb68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericDelaporte@users.noreply.github.com> Date: Sun, 2 Jul 2023 17:43:19 +0200 Subject: [PATCH 4/4] Release 5.3.18 (#3346) --- build-common/NHibernate.props | 2 +- releasenotes.txt | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/build-common/NHibernate.props b/build-common/NHibernate.props index 84b900b1f9f..8dc75dd47a5 100644 --- a/build-common/NHibernate.props +++ b/build-common/NHibernate.props @@ -5,7 +5,7 @@ 5.3 18 - dev + $(NhVersion).$(VersionPatch) $(VersionSuffix).$(BuildNumber) diff --git a/releasenotes.txt b/releasenotes.txt index 1224428c9b6..fd095e4ef4d 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -1,4 +1,21 @@ -Build 5.3.17 +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