Skip to content

Commit 39a28aa

Browse files
Fix #2608 - persisting a one-to-one with delayed insert fails (#2609)
Co-authored-by: Roman Artiukhin <[email protected]>
1 parent a966226 commit 39a28aa

File tree

15 files changed

+231
-7
lines changed

15 files changed

+231
-7
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using NUnit.Framework;
12+
13+
namespace NHibernate.Test.NHSpecificTest.GH2608
14+
{
15+
using System.Threading.Tasks;
16+
[TestFixture]
17+
public class FixtureAsync : BugTestCase
18+
{
19+
protected override void OnTearDown()
20+
{
21+
using (var session = OpenSession())
22+
using (var transaction = session.BeginTransaction())
23+
{
24+
session.CreateQuery("delete from PersonalDetails").ExecuteUpdate();
25+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
26+
27+
transaction.Commit();
28+
}
29+
}
30+
31+
[Test]
32+
public async Task MergeBidiPrimaryKeyOneToOneAsync()
33+
{
34+
using (var s = OpenSession())
35+
using (var tx = s.BeginTransaction())
36+
{
37+
var p = new Person { Name = "steve" };
38+
p.Details = new PersonalDetails { SomePersonalDetail = "I have big feet", Person = p };
39+
await (s.MergeAsync(p));
40+
await (tx.CommitAsync());
41+
}
42+
}
43+
44+
[Test]
45+
public async Task PersistBidiPrimaryKeyOneToOneAsync()
46+
{
47+
using (var s = OpenSession())
48+
using (var tx = s.BeginTransaction())
49+
{
50+
var p = new Person { Name = "steve" };
51+
p.Details = new PersonalDetails { SomePersonalDetail = "I have big feet", Person = p };
52+
await (s.PersistAsync(p));
53+
await (tx.CommitAsync());
54+
}
55+
}
56+
}
57+
}

src/NHibernate.Test/Async/Operations/MergeFixture.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ protected override void OnTearDown()
5050
await (s.DeleteAsync("from Competition", cancellationToken));
5151

5252
await (s.DeleteAsync("from Employer", cancellationToken));
53+
await (s.DeleteAsync("from Address", cancellationToken));
54+
await (s.DeleteAsync("from Person", cancellationToken));
5355

5456
await (tx.CommitAsync(cancellationToken));
5557
}
@@ -76,6 +78,8 @@ private void Cleanup()
7678
s.Delete("from Competition");
7779

7880
s.Delete("from Employer");
81+
s.Delete("from Address");
82+
s.Delete("from Person");
7983

8084
tx.Commit();
8185
}
@@ -156,6 +160,41 @@ public async Task MergeBidiForeignKeyOneToOneAsync()
156160
}
157161
}
158162

163+
[Test]
164+
public async Task MergeBidiPrimayKeyOneToOneAsync()
165+
{
166+
Person p;
167+
using (ISession s = OpenSession())
168+
using (ITransaction tx = s.BeginTransaction())
169+
{
170+
p = new Person {Name = "steve"};
171+
new PersonalDetails {SomePersonalDetail = "I have big feet", Person = p};
172+
await (s.PersistAsync(p));
173+
await (tx.CommitAsync());
174+
}
175+
176+
ClearCounts();
177+
178+
p.Details.SomePersonalDetail = p.Details.SomePersonalDetail + " and big hands too";
179+
using (ISession s = OpenSession())
180+
using (ITransaction tx = s.BeginTransaction())
181+
{
182+
p = (Person) await (s.MergeAsync(p));
183+
await (tx.CommitAsync());
184+
}
185+
186+
AssertInsertCount(0);
187+
AssertUpdateCount(1);
188+
AssertDeleteCount(0);
189+
190+
using (ISession s = OpenSession())
191+
using (ITransaction tx = s.BeginTransaction())
192+
{
193+
await (s.DeleteAsync(p));
194+
await (tx.CommitAsync());
195+
}
196+
}
197+
159198
[Test]
160199
public async Task MergeDeepTreeAsync()
161200
{
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using NUnit.Framework;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH2608
4+
{
5+
[TestFixture]
6+
public class Fixture : BugTestCase
7+
{
8+
protected override void OnTearDown()
9+
{
10+
using (var session = OpenSession())
11+
using (var transaction = session.BeginTransaction())
12+
{
13+
session.CreateQuery("delete from PersonalDetails").ExecuteUpdate();
14+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
15+
16+
transaction.Commit();
17+
}
18+
}
19+
20+
[Test]
21+
public void MergeBidiPrimaryKeyOneToOne()
22+
{
23+
using (var s = OpenSession())
24+
using (var tx = s.BeginTransaction())
25+
{
26+
var p = new Person { Name = "steve" };
27+
p.Details = new PersonalDetails { SomePersonalDetail = "I have big feet", Person = p };
28+
s.Merge(p);
29+
tx.Commit();
30+
}
31+
}
32+
33+
[Test]
34+
public void PersistBidiPrimaryKeyOneToOne()
35+
{
36+
using (var s = OpenSession())
37+
using (var tx = s.BeginTransaction())
38+
{
39+
var p = new Person { Name = "steve" };
40+
p.Details = new PersonalDetails { SomePersonalDetail = "I have big feet", Person = p };
41+
s.Persist(p);
42+
tx.Commit();
43+
}
44+
}
45+
}
46+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3+
assembly="NHibernate.Test"
4+
namespace="NHibernate.Test.NHSpecificTest.GH2608">
5+
6+
<class name="Person">
7+
<id name="Id">
8+
<generator class="native"/>
9+
</id>
10+
<property name="Name"/>
11+
<one-to-one name="Details" class="PersonalDetails" cascade="all"/>
12+
</class>
13+
14+
<class name="PersonalDetails">
15+
<id name="Id">
16+
<generator class="foreign">
17+
<param name="property">Person</param>
18+
</generator>
19+
</id>
20+
<property name="SomePersonalDetail"/>
21+
<one-to-one name="Person" class="Person" constrained="true"/>
22+
</class>
23+
24+
</hibernate-mapping>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace NHibernate.Test.NHSpecificTest.GH2608
2+
{
3+
public class Person
4+
{
5+
public virtual long Id { get; set; }
6+
public virtual string Name { get; set; }
7+
public virtual PersonalDetails Details { get; set; }
8+
}
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace NHibernate.Test.NHSpecificTest.GH2608
2+
{
3+
public class PersonalDetails
4+
{
5+
public virtual long Id { get; set; }
6+
public virtual string SomePersonalDetail { get; set; }
7+
8+
public virtual Person Person { get; set; }
9+
}
10+
}

src/NHibernate.Test/Operations/MergeFixture.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ private void Cleanup()
3838
s.Delete("from Competition");
3939

4040
s.Delete("from Employer");
41+
s.Delete("from Address");
42+
s.Delete("from Person");
4143

4244
tx.Commit();
4345
}
@@ -118,7 +120,7 @@ public void MergeBidiForeignKeyOneToOne()
118120
}
119121
}
120122

121-
[Test, Ignore("Need some more investigation about id sync.")]
123+
[Test]
122124
public void MergeBidiPrimayKeyOneToOne()
123125
{
124126
Person p;

src/NHibernate.Test/Operations/OneToOne.hbm.xml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@
3232

3333
<class name="PersonalDetails" table="OPS_PERS_DETAIL">
3434
<id name="Id" column="ID" type="long">
35-
<generator class="increment"/>
35+
<generator class="foreign">
36+
<param name="property">Person</param>
37+
</generator>
3638
</id>
3739
<property name="SomePersonalDetail" column="SOME_DETAIL" type="string"/>
3840
<one-to-one name="Person" class="Person" constrained="true" />
3941
</class>
4042

41-
</hibernate-mapping>
43+
</hibernate-mapping>

src/NHibernate/Action/DelayedPostInsertIdentifier.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,10 @@ public override string ToString()
4545
{
4646
return string.Format("<delayed:{0}>", sequence);
4747
}
48+
49+
/// <summary>
50+
/// The actual identifier value that has been generated.
51+
/// </summary>
52+
public object ActualId { get; set; }
4853
}
4954
}

src/NHibernate/Action/EntityInsertAction.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,19 @@ public override void Execute()
4444

4545
bool veto = PreInsert();
4646

47+
var wasDelayed = false;
4748
// Don't need to lock the cache here, since if someone
4849
// else inserted the same pk first, the insert would fail
4950
if (!veto)
5051
{
52+
// The identifier may be a foreign delayed identifier, which at this point should have been resolved.
53+
if (id is DelayedPostInsertIdentifier delayed)
54+
{
55+
wasDelayed = true;
56+
id = delayed.ActualId ??
57+
throw new InvalidOperationException(
58+
$"The delayed foreign identifier {delayed} has not been resolved before insertion of a {instance}");
59+
}
5160
persister.Insert(id, State, instance, Session);
5261

5362
EntityEntry entry = Session.PersistenceContext.GetEntry(instance);
@@ -57,6 +66,10 @@ public override void Execute()
5766
}
5867

5968
entry.PostInsert();
69+
if (wasDelayed)
70+
{
71+
Session.PersistenceContext.ReplaceDelayedEntityIdentityInsertKeys(entry.EntityKey, id);
72+
}
6073

6174
if (persister.HasInsertGeneratedProperties)
6275
{

src/NHibernate/Async/Action/EntityInsertAction.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,19 @@ public override async Task ExecuteAsync(CancellationToken cancellationToken)
4040

4141
bool veto = await (PreInsertAsync(cancellationToken)).ConfigureAwait(false);
4242

43+
var wasDelayed = false;
4344
// Don't need to lock the cache here, since if someone
4445
// else inserted the same pk first, the insert would fail
4546
if (!veto)
4647
{
48+
// The identifier may be a foreign delayed identifier, which at this point should have been resolved.
49+
if (id is DelayedPostInsertIdentifier delayed)
50+
{
51+
wasDelayed = true;
52+
id = delayed.ActualId ??
53+
throw new InvalidOperationException(
54+
$"The delayed foreign identifier {delayed} has not been resolved before insertion of a {instance}");
55+
}
4756
await (persister.InsertAsync(id, State, instance, Session, cancellationToken)).ConfigureAwait(false);
4857

4958
EntityEntry entry = Session.PersistenceContext.GetEntry(instance);
@@ -53,6 +62,10 @@ public override async Task ExecuteAsync(CancellationToken cancellationToken)
5362
}
5463

5564
entry.PostInsert();
65+
if (wasDelayed)
66+
{
67+
Session.PersistenceContext.ReplaceDelayedEntityIdentityInsertKeys(entry.EntityKey, id);
68+
}
5669

5770
if (persister.HasInsertGeneratedProperties)
5871
{

src/NHibernate/Async/Engine/StatefulPersistenceContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
using System.Collections.Generic;
1414
using System.Runtime.Serialization;
1515
using System.Security;
16-
using System.Security.Permissions;
1716
using System.Text;
17+
using NHibernate.Action;
1818
using NHibernate.Collection;
1919
using NHibernate.Engine.Loading;
2020
using NHibernate.Impl;

src/NHibernate/Async/Event/Default/AbstractSaveEventListener.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ protected virtual async Task<object> PerformSaveAsync(object entity, object id,
146146
throw new NonUniqueObjectException(id, persister.EntityName);
147147
}
148148
}
149-
persister.SetIdentifier(entity, id);
149+
if (!(id is DelayedPostInsertIdentifier))
150+
persister.SetIdentifier(entity, id);
150151
}
151152
else
152153
{

src/NHibernate/Engine/StatefulPersistenceContext.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
using System.Collections.Generic;
44
using System.Runtime.Serialization;
55
using System.Security;
6-
using System.Security.Permissions;
76
using System.Text;
7+
using NHibernate.Action;
88
using NHibernate.Collection;
99
using NHibernate.Engine.Loading;
1010
using NHibernate.Impl;
@@ -1392,6 +1392,8 @@ public void ReplaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, object gene
13921392
AddEntity(newKey, entity);
13931393
AddEntry(entity, oldEntry.Status, oldEntry.LoadedState, oldEntry.RowId, generatedId, oldEntry.Version,
13941394
oldEntry.LockMode, oldEntry.ExistsInDatabase, oldEntry.Persister, oldEntry.IsBeingReplicated);
1395+
if (oldKey.Identifier is DelayedPostInsertIdentifier delayed)
1396+
delayed.ActualId = generatedId;
13951397
}
13961398

13971399
public bool IsLoadFinished

src/NHibernate/Event/Default/AbstractSaveEventListener.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ protected virtual object PerformSave(object entity, object id, IEntityPersister
173173
throw new NonUniqueObjectException(id, persister.EntityName);
174174
}
175175
}
176-
persister.SetIdentifier(entity, id);
176+
if (!(id is DelayedPostInsertIdentifier))
177+
persister.SetIdentifier(entity, id);
177178
}
178179
else
179180
{

0 commit comments

Comments
 (0)