diff --git a/src/NHibernate.Test/Async/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs b/src/NHibernate.Test/Async/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs index b927eeb17b9..9756052f341 100644 --- a/src/NHibernate.Test/Async/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs +++ b/src/NHibernate.Test/Async/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs @@ -8,7 +8,9 @@ //------------------------------------------------------------------------------ +using System.Linq; using NHibernate.Cfg; +using NHibernate.Exceptions; using NUnit.Framework; namespace NHibernate.Test.Generatedkeys.Identity @@ -38,17 +40,238 @@ protected override void Configure(Configuration configuration) configuration.SetProperty(Environment.GenerateStatistics, "true"); } + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from MyChild").ExecuteUpdate(); + s.CreateQuery("delete from MySibling").ExecuteUpdate(); + s.CreateQuery("delete from System.Object").ExecuteUpdate(); + t.Commit(); + s.Close(); + } + } + [Test] public async Task IdentityColumnGeneratedIdsAsync() { using (var s = OpenSession()) using (var t = s.BeginTransaction()) { - MyEntity myEntity = new MyEntity("test"); - long id = (long) await (s.SaveAsync(myEntity)); - Assert.IsNotNull(id, "identity column did not force immediate insert"); - Assert.AreEqual(id, myEntity.Id); - await (s.DeleteAsync(myEntity)); + var entity1 = new MyEntity("test"); + var id1 = (long) await (s.SaveAsync(entity1)); + var entity2 = new MyEntity("test2"); + var id2 = (long) await (s.SaveAsync(entity2)); + // As 0 may be a valid identity value, we check for returned ids being not the same when saving two entities. + Assert.That(id1, Is.Not.EqualTo(id2), "identity column did not force immediate insert"); + Assert.That(id1, Is.EqualTo(entity1.Id)); + Assert.That(id2, Is.EqualTo(entity2.Id)); + await (t.CommitAsync()); + s.Close(); + } + } + + [Test] + public async Task PersistOutsideTransactionAsync() + { + var myEntity1 = new MyEntity("test-save"); + var myEntity2 = new MyEntity("test-persist"); + using (var s = OpenSession()) + { + // first test save() which should force an immediate insert... + var initialInsertCount = Sfi.Statistics.EntityInsertCount; + var id = (long) await (s.SaveAsync(myEntity1)); + Assert.That( + Sfi.Statistics.EntityInsertCount, + Is.GreaterThan(initialInsertCount), + "identity column did not force immediate insert"); + Assert.That(id, Is.EqualTo(myEntity1.Id)); + + // next test persist() which should cause a delayed insert... + initialInsertCount = Sfi.Statistics.EntityInsertCount; + await (s.PersistAsync(myEntity2)); + Assert.AreEqual( + initialInsertCount, + Sfi.Statistics.EntityInsertCount, + "persist on identity column not delayed"); + Assert.AreEqual(0, myEntity2.Id); + + // an explicit flush should cause execution of the delayed insertion + await (s.FlushAsync()); + Assert.AreEqual( + initialInsertCount + 1, + Sfi.Statistics.EntityInsertCount, + "delayed persist insert not executed on flush"); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync(myEntity1)); + await (s.DeleteAsync(myEntity2)); + await (t.CommitAsync()); + s.Close(); + } + } + + [Test] + public async Task PersistOutsideTransactionCascadedToNonInverseCollectionAsync() + { + long initialInsertCount = Sfi.Statistics.EntityInsertCount; + using (var s = OpenSession()) + { + MyEntity myEntity = new MyEntity("test-persist"); + myEntity.NonInverseChildren.Add(new MyChild("test-child-persist-non-inverse")); + await (s.PersistAsync(myEntity)); + Assert.AreEqual( + initialInsertCount, + Sfi.Statistics.EntityInsertCount, + "persist on identity column not delayed"); + Assert.AreEqual(0, myEntity.Id); + await (s.FlushAsync()); + Assert.AreEqual( + initialInsertCount + 2, + Sfi.Statistics.EntityInsertCount, + "delayed persist insert not executed on flush"); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync("from MyChild")); + await (s.DeleteAsync("from MyEntity")); + await (t.CommitAsync()); + s.Close(); + } + } + + [Test] + public async Task PersistOutsideTransactionCascadedToInverseCollectionAsync() + { + long initialInsertCount = Sfi.Statistics.EntityInsertCount; + using (var s = OpenSession()) + { + MyEntity myEntity2 = new MyEntity("test-persist-2"); + MyChild child = new MyChild("test-child-persist-inverse"); + myEntity2.InverseChildren.Add(child); + child.InverseParent = myEntity2; + await (s.PersistAsync(myEntity2)); + Assert.AreEqual( + initialInsertCount, + Sfi.Statistics.EntityInsertCount, + "persist on identity column not delayed"); + Assert.AreEqual(0, myEntity2.Id); + await (s.FlushAsync()); + Assert.AreEqual( + initialInsertCount + 2, + Sfi.Statistics.EntityInsertCount, + "delayed persist insert not executed on flush"); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync("from MyChild")); + await (s.DeleteAsync("from MyEntity")); + await (t.CommitAsync()); + s.Close(); + } + } + + [Test] + public async Task PersistOutsideTransactionCascadedToManyToOneAsync() + { + long initialInsertCount = Sfi.Statistics.EntityInsertCount; + using (var s = OpenSession()) + { + MyEntity myEntity = new MyEntity("test-persist"); + myEntity.Sibling = new MySibling("test-persist-sibling-out"); + await (s.PersistAsync(myEntity)); + Assert.AreEqual( + initialInsertCount, + Sfi.Statistics.EntityInsertCount, + "persist on identity column not delayed"); + Assert.AreEqual(0, myEntity.Id); + await (s.FlushAsync()); + Assert.AreEqual( + initialInsertCount + 2, + Sfi.Statistics.EntityInsertCount, + "delayed persist insert not executed on flush"); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync("from MyEntity")); + await (s.DeleteAsync("from MySibling")); + await (t.CommitAsync()); + s.Close(); + } + } + + [Test] + public async Task PersistOutsideTransactionCascadedFromManyToOneAsync() + { + long initialInsertCount = Sfi.Statistics.EntityInsertCount; + using (var s = OpenSession()) + { + MyEntity myEntity2 = new MyEntity("test-persist-2"); + MySibling sibling = new MySibling("test-persist-sibling-in"); + sibling.Entity = myEntity2; + await (s.PersistAsync(sibling)); + Assert.AreEqual( + initialInsertCount, + Sfi.Statistics.EntityInsertCount, + "persist on identity column not delayed"); + Assert.AreEqual(0, myEntity2.Id); + await (s.FlushAsync()); + Assert.AreEqual( + initialInsertCount + 2, + Sfi.Statistics.EntityInsertCount, + "delayed persist insert not executed on flush"); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync("from MySibling")); + await (s.DeleteAsync("from MyEntity")); + await (t.CommitAsync()); + s.Close(); + } + } + + [Test] + public async Task QueryOnPersistedEntityAsync([Values(FlushMode.Auto, FlushMode.Commit)] FlushMode flushMode) + { + var myEntity = new MyEntity("test-persist"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.FlushMode = flushMode; + + var initialInsertCount = Sfi.Statistics.EntityInsertCount; + await (s.PersistAsync(myEntity)); + Assert.That(Sfi.Statistics.EntityInsertCount, Is.EqualTo(initialInsertCount), + "persist on identity column not delayed"); + Assert.That(myEntity.Id, Is.Zero); + + var query = s.Query().Where(c => c.InverseParent == myEntity); + switch (flushMode) + { + case FlushMode.Auto: + Assert.That(query.ToList, Throws.Nothing); + break; + case FlushMode.Commit: + Assert.That(query.ToList, Throws.Exception.TypeOf(typeof(UnresolvableObjectException))); + break; + } await (t.CommitAsync()); s.Close(); } diff --git a/src/NHibernate.Test/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs b/src/NHibernate.Test/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs index 3eaf6de36ca..7eb3f8eb7d6 100644 --- a/src/NHibernate.Test/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs +++ b/src/NHibernate.Test/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs @@ -1,4 +1,6 @@ +using System.Linq; using NHibernate.Cfg; +using NHibernate.Exceptions; using NUnit.Framework; namespace NHibernate.Test.Generatedkeys.Identity @@ -27,23 +29,39 @@ protected override void Configure(Configuration configuration) configuration.SetProperty(Environment.GenerateStatistics, "true"); } + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from MyChild").ExecuteUpdate(); + s.CreateQuery("delete from MySibling").ExecuteUpdate(); + s.CreateQuery("delete from System.Object").ExecuteUpdate(); + t.Commit(); + s.Close(); + } + } + [Test] public void IdentityColumnGeneratedIds() { using (var s = OpenSession()) using (var t = s.BeginTransaction()) { - MyEntity myEntity = new MyEntity("test"); - long id = (long) s.Save(myEntity); - Assert.IsNotNull(id, "identity column did not force immediate insert"); - Assert.AreEqual(id, myEntity.Id); - s.Delete(myEntity); + var entity1 = new MyEntity("test"); + var id1 = (long) s.Save(entity1); + var entity2 = new MyEntity("test2"); + var id2 = (long) s.Save(entity2); + // As 0 may be a valid identity value, we check for returned ids being not the same when saving two entities. + Assert.That(id1, Is.Not.EqualTo(id2), "identity column did not force immediate insert"); + Assert.That(id1, Is.EqualTo(entity1.Id)); + Assert.That(id2, Is.EqualTo(entity2.Id)); t.Commit(); s.Close(); } } - [Test, Ignore("Not supported yet.")] + [Test] public void PersistOutsideTransaction() { var myEntity1 = new MyEntity("test-save"); @@ -51,12 +69,16 @@ public void PersistOutsideTransaction() using (var s = OpenSession()) { // first test save() which should force an immediate insert... - long id = (long) s.Save(myEntity1); - Assert.IsNotNull(id, "identity column did not force immediate insert"); - Assert.AreEqual(id, myEntity1.Id); + var initialInsertCount = Sfi.Statistics.EntityInsertCount; + var id = (long) s.Save(myEntity1); + Assert.That( + Sfi.Statistics.EntityInsertCount, + Is.GreaterThan(initialInsertCount), + "identity column did not force immediate insert"); + Assert.That(id, Is.EqualTo(myEntity1.Id)); // next test persist() which should cause a delayed insert... - long initialInsertCount = Sfi.Statistics.EntityInsertCount; + initialInsertCount = Sfi.Statistics.EntityInsertCount; s.Persist(myEntity2); Assert.AreEqual( initialInsertCount, @@ -83,7 +105,7 @@ public void PersistOutsideTransaction() } } - [Test, Ignore("Not supported yet.")] + [Test] public void PersistOutsideTransactionCascadedToNonInverseCollection() { long initialInsertCount = Sfi.Statistics.EntityInsertCount; @@ -115,7 +137,7 @@ public void PersistOutsideTransactionCascadedToNonInverseCollection() } } - [Test, Ignore("Not supported yet.")] + [Test] public void PersistOutsideTransactionCascadedToInverseCollection() { long initialInsertCount = Sfi.Statistics.EntityInsertCount; @@ -149,7 +171,7 @@ public void PersistOutsideTransactionCascadedToInverseCollection() } } - [Test, Ignore("Not supported yet.")] + [Test] public void PersistOutsideTransactionCascadedToManyToOne() { long initialInsertCount = Sfi.Statistics.EntityInsertCount; @@ -181,7 +203,7 @@ public void PersistOutsideTransactionCascadedToManyToOne() } } - [Test, Ignore("Not supported yet.")] + [Test] public void PersistOutsideTransactionCascadedFromManyToOne() { long initialInsertCount = Sfi.Statistics.EntityInsertCount; @@ -213,5 +235,35 @@ public void PersistOutsideTransactionCascadedFromManyToOne() s.Close(); } } + + [Test] + public void QueryOnPersistedEntity([Values(FlushMode.Auto, FlushMode.Commit)] FlushMode flushMode) + { + var myEntity = new MyEntity("test-persist"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.FlushMode = flushMode; + + var initialInsertCount = Sfi.Statistics.EntityInsertCount; + s.Persist(myEntity); + Assert.That(Sfi.Statistics.EntityInsertCount, Is.EqualTo(initialInsertCount), + "persist on identity column not delayed"); + Assert.That(myEntity.Id, Is.Zero); + + var query = s.Query().Where(c => c.InverseParent == myEntity); + switch (flushMode) + { + case FlushMode.Auto: + Assert.That(query.ToList, Throws.Nothing); + break; + case FlushMode.Commit: + Assert.That(query.ToList, Throws.Exception.TypeOf(typeof(UnresolvableObjectException))); + break; + } + t.Commit(); + s.Close(); + } + } } } diff --git a/src/NHibernate.Test/Generatedkeys/Identity/MyEntity.hbm.xml b/src/NHibernate.Test/Generatedkeys/Identity/MyEntity.hbm.xml index 0b1deccdfdb..00918675ee8 100644 --- a/src/NHibernate.Test/Generatedkeys/Identity/MyEntity.hbm.xml +++ b/src/NHibernate.Test/Generatedkeys/Identity/MyEntity.hbm.xml @@ -1,45 +1,45 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + assembly="NHibernate.Test" + namespace="NHibernate.Test.Generatedkeys.Identity"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate/Async/Type/EntityType.cs b/src/NHibernate/Async/Type/EntityType.cs index 95fa8eaa2bb..dffe868962e 100644 --- a/src/NHibernate/Async/Type/EntityType.cs +++ b/src/NHibernate/Async/Type/EntityType.cs @@ -18,6 +18,7 @@ using NHibernate.Proxy; using NHibernate.Util; using System.Collections.Generic; +using NHibernate.Action; namespace NHibernate.Type { @@ -80,6 +81,20 @@ protected internal async Task GetReferenceValueAsync(object value, ISess } } + protected internal async Task GetReferenceValueAsync(object value, ISessionImplementor session, bool forbidDelayed, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var referenceValue = await (GetReferenceValueAsync(value, session, cancellationToken)).ConfigureAwait(false); + if (forbidDelayed && referenceValue is DelayedPostInsertIdentifier) + { + throw new UnresolvableObjectException( + $"Cannot resolve the identifier from a {nameof(DelayedPostInsertIdentifier)}. Consider flushing the session first.", + referenceValue, returnedClass); + } + + return referenceValue; + } + public override async Task ReplaceAsync(object original, object target, ISessionImplementor session, object owner, IDictionary copyCache, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Type/ManyToOneType.cs b/src/NHibernate/Async/Type/ManyToOneType.cs index d5615a85d7a..66c332ffe6c 100644 --- a/src/NHibernate/Async/Type/ManyToOneType.cs +++ b/src/NHibernate/Async/Type/ManyToOneType.cs @@ -26,14 +26,14 @@ public override async Task NullSafeSetAsync(DbCommand st, object value, int inde { cancellationToken.ThrowIfCancellationRequested(); await (GetIdentifierOrUniqueKeyType(session.Factory) - .NullSafeSetAsync(st, await (GetReferenceValueAsync(value, session, cancellationToken)).ConfigureAwait(false), index, settable, session, cancellationToken)).ConfigureAwait(false); + .NullSafeSetAsync(st, await (GetReferenceValueAsync(value, session, true, cancellationToken)).ConfigureAwait(false), index, settable, session, cancellationToken)).ConfigureAwait(false); } public override async Task NullSafeSetAsync(DbCommand cmd, object value, int index, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); await (GetIdentifierOrUniqueKeyType(session.Factory) - .NullSafeSetAsync(cmd, await (GetReferenceValueAsync(value, session, cancellationToken)).ConfigureAwait(false), index, session, cancellationToken)).ConfigureAwait(false); + .NullSafeSetAsync(cmd, await (GetReferenceValueAsync(value, session, true, cancellationToken)).ConfigureAwait(false), index, session, cancellationToken)).ConfigureAwait(false); } /// diff --git a/src/NHibernate/Async/Type/OneToOneType.cs b/src/NHibernate/Async/Type/OneToOneType.cs index d53f9c968d2..dc5edd36b36 100644 --- a/src/NHibernate/Async/Type/OneToOneType.cs +++ b/src/NHibernate/Async/Type/OneToOneType.cs @@ -43,7 +43,7 @@ public override async Task NullSafeSetAsync(DbCommand cmd, object value, int ind { cancellationToken.ThrowIfCancellationRequested(); await (GetIdentifierOrUniqueKeyType(session.Factory) - .NullSafeSetAsync(cmd, await (GetReferenceValueAsync(value, session, cancellationToken)).ConfigureAwait(false), index, session, cancellationToken)).ConfigureAwait(false); + .NullSafeSetAsync(cmd, await (GetReferenceValueAsync(value, session, true, cancellationToken)).ConfigureAwait(false), index, session, cancellationToken)).ConfigureAwait(false); } public override Task IsDirtyAsync(object old, object current, ISessionImplementor session, CancellationToken cancellationToken) diff --git a/src/NHibernate/Type/EntityType.cs b/src/NHibernate/Type/EntityType.cs index b45502fc914..5d5b3e29e90 100644 --- a/src/NHibernate/Type/EntityType.cs +++ b/src/NHibernate/Type/EntityType.cs @@ -8,6 +8,7 @@ using NHibernate.Proxy; using NHibernate.Util; using System.Collections.Generic; +using NHibernate.Action; namespace NHibernate.Type { @@ -185,6 +186,19 @@ protected internal object GetReferenceValue(object value, ISessionImplementor se } } + protected internal object GetReferenceValue(object value, ISessionImplementor session, bool forbidDelayed) + { + var referenceValue = GetReferenceValue(value, session); + if (forbidDelayed && referenceValue is DelayedPostInsertIdentifier) + { + throw new UnresolvableObjectException( + $"Cannot resolve the identifier from a {nameof(DelayedPostInsertIdentifier)}. Consider flushing the session first.", + referenceValue, returnedClass); + } + + return referenceValue; + } + public override string ToLoggableString(object value, ISessionFactoryImplementor factory) { if (value == null) diff --git a/src/NHibernate/Type/ManyToOneType.cs b/src/NHibernate/Type/ManyToOneType.cs index 172bc8f1b99..47e01b10269 100644 --- a/src/NHibernate/Type/ManyToOneType.cs +++ b/src/NHibernate/Type/ManyToOneType.cs @@ -58,13 +58,13 @@ public override SqlType[] SqlTypes(IMapping mapping) public override void NullSafeSet(DbCommand st, object value, int index, bool[] settable, ISessionImplementor session) { GetIdentifierOrUniqueKeyType(session.Factory) - .NullSafeSet(st, GetReferenceValue(value, session), index, settable, session); + .NullSafeSet(st, GetReferenceValue(value, session, true), index, settable, session); } public override void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session) { GetIdentifierOrUniqueKeyType(session.Factory) - .NullSafeSet(cmd, GetReferenceValue(value, session), index, session); + .NullSafeSet(cmd, GetReferenceValue(value, session, true), index, session); } public override bool IsOneToOne diff --git a/src/NHibernate/Type/OneToOneType.cs b/src/NHibernate/Type/OneToOneType.cs index 0837ea3b98a..7117b8bc329 100644 --- a/src/NHibernate/Type/OneToOneType.cs +++ b/src/NHibernate/Type/OneToOneType.cs @@ -49,7 +49,7 @@ public override void NullSafeSet(DbCommand st, object value, int index, bool[] s public override void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session) { GetIdentifierOrUniqueKeyType(session.Factory) - .NullSafeSet(cmd, GetReferenceValue(value, session), index, session); + .NullSafeSet(cmd, GetReferenceValue(value, session, true), index, session); } public override bool IsOneToOne