diff --git a/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs b/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs index f4807ca8e85..cb8dc0298de 100644 --- a/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs +++ b/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs @@ -9,6 +9,7 @@ using System.Collections; +using System.Collections.Generic; using System.Linq; using NHibernate.Cfg; using NHibernate.Intercept; @@ -81,6 +82,7 @@ protected override void OnTearDown() using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { + s.CreateQuery("delete from Word").ExecuteUpdate(); s.CreateQuery("delete from Book").ExecuteUpdate(); tx.Commit(); } @@ -345,5 +347,56 @@ public async Task CacheShouldNotContainLazyPropertiesAsync() Assert.That(NHibernateUtil.IsPropertyInitialized(book, "ALotOfText"), Is.False); Assert.That(NHibernateUtil.IsPropertyInitialized(book, "Image"), Is.False); } + + [Test] + public async Task CanMergeTransientWithLazyPropertyInCollectionAsync() + { + Book book; + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + book = new Book + { + Name = "some name two", + Id = 3, + ALotOfText = "a lot of text two..." + }; + // This should insert a new entity. + await (s.MergeAsync(book)); + await (tx.CommitAsync()); + } + + using (var s = OpenSession()) + { + book = await (s.GetAsync(3)); + Assert.That(book, Is.Not.Null); + Assert.That(book.Name, Is.EqualTo("some name two")); + Assert.That(book.ALotOfText, Is.EqualTo("a lot of text two...")); + + } + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + book.Words = new List(); + var word = new Word + { + Id = 2, + Parent = book, + Content = new byte[1] {0} + }; + + book.Words.Add(word); + await (s.MergeAsync(book)); + await (tx.CommitAsync()); + } + + using (var s = OpenSession()) + { + book = await (s.GetAsync(3)); + Assert.That(book.Words.Any(), Is.True); + Assert.That(book.Words.First().Content, Is.EqualTo(new byte[1] { 0 })); + } + } } } diff --git a/src/NHibernate.Test/LazyProperty/Book.cs b/src/NHibernate.Test/LazyProperty/Book.cs index c4fdf9c5639..546df8a248b 100644 --- a/src/NHibernate.Test/LazyProperty/Book.cs +++ b/src/NHibernate.Test/LazyProperty/Book.cs @@ -1,4 +1,6 @@ -namespace NHibernate.Test.LazyProperty +using System.Collections.Generic; + +namespace NHibernate.Test.LazyProperty { public class Book { @@ -16,5 +18,7 @@ public virtual string ALotOfText public virtual byte[] Image { get; set; } 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 c70e46ed224..af65b929e69 100644 --- a/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs +++ b/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using System.Linq; using NHibernate.Cfg; using NHibernate.Intercept; @@ -68,6 +69,7 @@ protected override void OnTearDown() using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { + s.CreateQuery("delete from Word").ExecuteUpdate(); s.CreateQuery("delete from Book").ExecuteUpdate(); tx.Commit(); } @@ -338,5 +340,56 @@ public void CacheShouldNotContainLazyProperties() Assert.That(NHibernateUtil.IsPropertyInitialized(book, "ALotOfText"), Is.False); Assert.That(NHibernateUtil.IsPropertyInitialized(book, "Image"), Is.False); } + + [Test] + public void CanMergeTransientWithLazyPropertyInCollection() + { + Book book; + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + book = new Book + { + Name = "some name two", + Id = 3, + ALotOfText = "a lot of text two..." + }; + // This should insert a new entity. + s.Merge(book); + tx.Commit(); + } + + using (var s = OpenSession()) + { + book = s.Get(3); + Assert.That(book, Is.Not.Null); + Assert.That(book.Name, Is.EqualTo("some name two")); + Assert.That(book.ALotOfText, Is.EqualTo("a lot of text two...")); + + } + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + book.Words = new List(); + var word = new Word + { + Id = 2, + Parent = book, + Content = new byte[1] {0} + }; + + book.Words.Add(word); + s.Merge(book); + tx.Commit(); + } + + using (var s = OpenSession()) + { + book = s.Get(3); + Assert.That(book.Words.Any(), Is.True); + Assert.That(book.Words.First().Content, Is.EqualTo(new byte[1] { 0 })); + } + } } } diff --git a/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml b/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml index 970fcaaedc0..91189c36e30 100644 --- a/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml +++ b/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml @@ -12,6 +12,18 @@ + + + + + + + + + + + + diff --git a/src/NHibernate.Test/LazyProperty/Word.cs b/src/NHibernate.Test/LazyProperty/Word.cs new file mode 100644 index 00000000000..125e8038023 --- /dev/null +++ b/src/NHibernate.Test/LazyProperty/Word.cs @@ -0,0 +1,9 @@ +namespace NHibernate.Test.LazyProperty +{ + public class Word + { + public virtual int Id { get; set; } + public virtual byte[] Content { get; set; } + public virtual Book Parent { get; set; } + } +} diff --git a/src/NHibernate/Async/Type/TypeHelper.cs b/src/NHibernate/Async/Type/TypeHelper.cs index 751418d51d4..4fc408ea24c 100644 --- a/src/NHibernate/Async/Type/TypeHelper.cs +++ b/src/NHibernate/Async/Type/TypeHelper.cs @@ -169,6 +169,25 @@ public static async Task ReplaceAsync(object[] original, object[] targ { copied[i] = target[i]; } + else if (target[i] == LazyPropertyInitializer.UnfetchedProperty) + { + // Should be no need to check for target[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN + // because PropertyAccessStrategyBackRefImpl.get( object ) returns + // PropertyAccessStrategyBackRefImpl.UNKNOWN, so target[i] == original[i]. + // + // We know from above that original[i] != LazyPropertyInitializer.UNFETCHED_PROPERTY && + // original[i] != PropertyAccessStrategyBackRefImpl.UNKNOWN; + // This is a case where the entity being merged has a lazy property + // that has been initialized. Copy the initialized value from original. + if (types[i].IsMutable) + { + copied[i] = types[i].DeepCopy(original[i], session.Factory); + } + else + { + copied[i] = original[i]; + } + } else copied[i] = await (types[i].ReplaceAsync(original[i], target[i], session, owner, copyCache, foreignKeyDirection, cancellationToken)).ConfigureAwait(false); } @@ -343,4 +362,4 @@ public static async Task FindModifiedAsync(StandardProperty[] properties, } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Type/TypeHelper.cs b/src/NHibernate/Type/TypeHelper.cs index dfb5c38d2ca..a447ef7f64e 100644 --- a/src/NHibernate/Type/TypeHelper.cs +++ b/src/NHibernate/Type/TypeHelper.cs @@ -175,6 +175,25 @@ public static object[] Replace(object[] original, object[] target, IType[] types { copied[i] = target[i]; } + else if (target[i] == LazyPropertyInitializer.UnfetchedProperty) + { + // Should be no need to check for target[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN + // because PropertyAccessStrategyBackRefImpl.get( object ) returns + // PropertyAccessStrategyBackRefImpl.UNKNOWN, so target[i] == original[i]. + // + // We know from above that original[i] != LazyPropertyInitializer.UNFETCHED_PROPERTY && + // original[i] != PropertyAccessStrategyBackRefImpl.UNKNOWN; + // This is a case where the entity being merged has a lazy property + // that has been initialized. Copy the initialized value from original. + if (types[i].IsMutable) + { + copied[i] = types[i].DeepCopy(original[i], session.Factory); + } + else + { + copied[i] = original[i]; + } + } else copied[i] = types[i].Replace(original[i], target[i], session, owner, copyCache, foreignKeyDirection); } @@ -342,4 +361,4 @@ public static int[] FindModified(StandardProperty[] properties, } } } -} \ No newline at end of file +}