diff --git a/releasenotes.txt b/releasenotes.txt index 7d995c663c0..7976dbf8b14 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -234,6 +234,23 @@ 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 ============================= 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/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/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index 128130c5b74..ab2af9c2392 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -798,8 +798,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 } @@ -815,7 +820,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 6a74b42032b..9635daff25d 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -1204,8 +1204,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 } @@ -1221,7 +1226,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/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);