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);