Skip to content

Commit fee1f14

Browse files
NH-2145 - NH-926 (re) - NH-3600 - identity: fix for releasing of connection between insert and identity retrieval.
1 parent 1c764f9 commit fee1f14

9 files changed

+225
-42
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace NHibernate.Test.IdTest
4+
{
5+
public class IdentityClass
6+
{
7+
public virtual int Id { get; set; }
8+
public virtual string Name { get; set; }
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3+
assembly="NHibernate.Test" namespace="NHibernate.Test.IdTest">
4+
<class name="IdentityClass">
5+
<id name="Id" type="int">
6+
<generator class="identity" />
7+
</id>
8+
<property name="Name" />
9+
</class>
10+
</hibernate-mapping>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Linq;
3+
using NHibernate.Linq;
4+
using NUnit.Framework;
5+
6+
namespace NHibernate.Test.IdTest
7+
{
8+
[TestFixture]
9+
public class IdentityGeneratorFixture : IdFixtureBase
10+
{
11+
protected override string TypeName
12+
{
13+
get { return "Identity"; }
14+
}
15+
16+
protected override bool AppliesTo(Dialect.Dialect dialect)
17+
{
18+
return dialect.SupportsIdentityColumns;
19+
}
20+
21+
protected override void OnTearDown()
22+
{
23+
using (var s = OpenSession())
24+
using (var t = s.BeginTransaction())
25+
{
26+
s.CreateQuery("delete System.Object").ExecuteUpdate();
27+
t.Commit();
28+
}
29+
}
30+
31+
[Test(Description = "NH-926")]
32+
public void NonTransactedInsert()
33+
{
34+
using (var s = OpenSession())
35+
{
36+
Assert.DoesNotThrow(() => s.Save(new IdentityClass()));
37+
// No need to flush with identity generator.
38+
Assert.AreEqual(1, s.Query<IdentityClass>().Count());
39+
}
40+
}
41+
42+
[Test]
43+
public void TransactedInsert()
44+
{
45+
using (var s = OpenSession())
46+
using (var t = s.BeginTransaction())
47+
{
48+
Assert.DoesNotThrow(() => s.Save(new IdentityClass()));
49+
// No need to flush with identity generator.
50+
Assert.AreEqual(1, s.Query<IdentityClass>().Count());
51+
t.Commit();
52+
}
53+
}
54+
}
55+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Linq;
3+
using NHibernate.Cfg;
4+
using NHibernate.Dialect;
5+
using NHibernate.Linq;
6+
using NUnit.Framework;
7+
8+
namespace NHibernate.Test.IdTest
9+
{
10+
[TestFixture]
11+
public class IdentityGeneratorNoPoolingFixture : IdFixtureBase
12+
{
13+
protected override string TypeName
14+
{
15+
get { return "Identity"; }
16+
}
17+
18+
protected override bool AppliesTo(Dialect.Dialect dialect)
19+
{
20+
return dialect.SupportsIdentityColumns && dialect.SupportsPoolingParameter;
21+
}
22+
23+
protected override void Configure(Configuration configuration)
24+
{
25+
var connectionString = configuration.GetProperty(Cfg.Environment.ConnectionString) +
26+
";Pooling=false";
27+
configuration.SetProperty(Cfg.Environment.ConnectionString, connectionString);
28+
}
29+
30+
protected override void OnTearDown()
31+
{
32+
using (var s = OpenSession())
33+
using (var t = s.BeginTransaction())
34+
{
35+
s.CreateQuery("delete System.Object").ExecuteUpdate();
36+
t.Commit();
37+
}
38+
}
39+
40+
[Test(Description = "NH-3600")]
41+
public void NonTransactedInsert()
42+
{
43+
using (var s = OpenSession())
44+
{
45+
Assert.DoesNotThrow(() => s.Save(new IdentityClass()));
46+
// No need to flush with identity generator.
47+
Assert.AreEqual(1, s.Query<IdentityClass>().Count());
48+
}
49+
}
50+
51+
[Test]
52+
public void TransactedInsert()
53+
{
54+
using (var s = OpenSession())
55+
using (var t = s.BeginTransaction())
56+
{
57+
Assert.DoesNotThrow(() => s.Save(new IdentityClass()));
58+
// No need to flush with identity generator.
59+
Assert.AreEqual(1, s.Query<IdentityClass>().Count());
60+
t.Commit();
61+
}
62+
}
63+
}
64+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,9 @@
474474
<Compile Include="IdGen\Enhanced\Table\PooledLoTableTest.cs" />
475475
<Compile Include="IdTest\AssignedClass.cs" />
476476
<Compile Include="IdTest\AssignedFixture.cs" />
477+
<Compile Include="IdTest\IdentityGeneratorNoPoolingFixture.cs" />
478+
<Compile Include="IdTest\IdentityGeneratorFixture.cs" />
479+
<Compile Include="IdTest\IdentityClass.cs" />
477480
<Compile Include="IdTest\TableGeneratorFixture.cs" />
478481
<Compile Include="Immutable\Contract.cs" />
479482
<Compile Include="Immutable\ContractVariation.cs" />
@@ -3272,6 +3275,7 @@
32723275
</ItemGroup>
32733276
<ItemGroup>
32743277
<EmbeddedResource Include="SessionBuilder\Mappings.hbm.xml" />
3278+
<EmbeddedResource Include="IdTest\IdentityClass.hbm.xml" />
32753279
<EmbeddedResource Include="NHSpecificTest\NH1904\StructMappings.hbm.xml" />
32763280
<EmbeddedResource Include="Insertordering\FamilyModel\Mappings.hbm.xml" />
32773281
<EmbeddedResource Include="Insertordering\AnimalModel\Mappings.hbm.xml" />

src/NHibernate/AdoNet/ConnectionManager.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Data.Common;
44
using System.Runtime.Serialization;
55
using System.Security;
6-
using System.Security.Permissions;
76

87
using NHibernate.Engine;
98

@@ -13,7 +12,7 @@ namespace NHibernate.AdoNet
1312
/// Manages the database connection and transaction for an <see cref="ISession" />.
1413
/// </summary>
1514
/// <remarks>
16-
/// This class corresponds to ConnectionManager and JDBCContext in Hibernate,
15+
/// This class corresponds to LogicalConnectionImplementor and JdbcCoordinator in Hibernate,
1716
/// combined.
1817
/// </remarks>
1918
[Serializable]
@@ -44,7 +43,7 @@ public interface Callback
4443
private readonly IInterceptor interceptor;
4544

4645
[NonSerialized]
47-
private bool isFlushing;
46+
private bool _releasesEnabled = true;
4847

4948
private bool flushingFromDtcTransaction;
5049

@@ -227,9 +226,9 @@ public void AfterStatement()
227226
{
228227
if (IsAggressiveRelease)
229228
{
230-
if (isFlushing)
229+
if (!_releasesEnabled)
231230
{
232-
log.Debug("skipping aggressive-release due to flush cycle");
231+
log.Debug("skipping aggressive-release due to manual disabling");
233232
}
234233
else if (batcher.HasOpenResources)
235234
{
@@ -259,16 +258,31 @@ private void AggressiveRelease()
259258
}
260259
}
261260

261+
[NonSerialized]
262+
private int _flushDepth;
263+
262264
public void FlushBeginning()
263265
{
264-
log.Debug("registering flush begin");
265-
isFlushing = true;
266+
if (_flushDepth == 0)
267+
{
268+
log.Debug("registering flush begin");
269+
_releasesEnabled = false;
270+
}
271+
_flushDepth++;
266272
}
267273

268274
public void FlushEnding()
269275
{
270-
log.Debug("registering flush end");
271-
isFlushing = false;
276+
_flushDepth--;
277+
if (_flushDepth < 0)
278+
{
279+
throw new HibernateException("Mismatched flush handling");
280+
}
281+
if (_flushDepth == 0)
282+
{
283+
_releasesEnabled = true;
284+
log.Debug("registering flush end");
285+
}
272286
AfterStatement();
273287
}
274288

src/NHibernate/Dialect/Dialect.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,6 +2058,11 @@ public virtual bool SupportsSubSelects
20582058
get { return true; }
20592059
}
20602060

2061+
/// <summary>
2062+
/// Does this dialect support pooling parameter in connection string?
2063+
/// </summary>
2064+
public virtual bool SupportsPoolingParameter => true;
2065+
20612066
#endregion
20622067

20632068
/// <summary>

src/NHibernate/Dialect/MsSqlCeDialect.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,5 +207,14 @@ public override long TimestampResolutionInTicks
207207
return TimeSpan.TicksPerMillisecond*10L;
208208
}
209209
}
210+
211+
#region Informational metadata
212+
213+
/// <summary>
214+
/// Does this dialect support pooling parameter in connection string?
215+
/// </summary>
216+
public override bool SupportsPoolingParameter => false;
217+
218+
#endregion
210219
}
211220
}

src/NHibernate/Id/Insert/AbstractSelectingDelegate.cs

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,57 +26,69 @@ protected internal AbstractSelectingDelegate(IPostInsertIdentityPersister persis
2626

2727
public abstract IdentifierGeneratingInsert PrepareIdentifierGeneratingInsert();
2828

29-
public object PerformInsert(SqlCommandInfo insertSQL, ISessionImplementor session, IBinder binder)
29+
public object PerformInsert(SqlCommandInfo insertSql, ISessionImplementor session, IBinder binder)
3030
{
31+
// NH-2145: Prevent connection releases between insert and select when we cannot perform
32+
// them as a single statement. Retrieving id most of the time relies on using the same connection.
33+
session.ConnectionManager.FlushBeginning();
3134
try
3235
{
33-
// prepare and execute the insert
34-
var insert = session.Batcher.PrepareCommand(insertSQL.CommandType, insertSQL.Text, insertSQL.ParameterTypes);
3536
try
3637
{
37-
binder.BindValues(insert);
38-
session.Batcher.ExecuteNonQuery(insert);
38+
// prepare and execute the insert
39+
var insert = session.Batcher.PrepareCommand(insertSql.CommandType, insertSql.Text, insertSql.ParameterTypes);
40+
try
41+
{
42+
binder.BindValues(insert);
43+
session.Batcher.ExecuteNonQuery(insert);
44+
}
45+
finally
46+
{
47+
session.Batcher.CloseCommand(insert, null);
48+
}
3949
}
40-
finally
50+
catch (DbException sqle)
4151
{
42-
session.Batcher.CloseCommand(insert, null);
52+
throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, sqle,
53+
"could not insert: " + persister.GetInfoString(), insertSql.Text);
4354
}
44-
}
45-
catch (DbException sqle)
46-
{
47-
throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, sqle,
48-
"could not insert: " + persister.GetInfoString(), insertSQL.Text);
49-
}
5055

51-
SqlString selectSQL = SelectSQL;
52-
using (new SessionIdLoggingContext(session.SessionId))
53-
try
54-
{
55-
//fetch the generated id in a separate query
56-
var idSelect = session.Batcher.PrepareCommand(CommandType.Text, selectSQL, ParametersTypes);
57-
try
56+
var selectSql = SelectSQL;
57+
using (new SessionIdLoggingContext(session.SessionId))
5858
{
59-
BindParameters(session, idSelect, binder.Entity);
60-
var rs = session.Batcher.ExecuteReader(idSelect);
6159
try
6260
{
63-
return GetResult(session, rs, binder.Entity);
61+
//fetch the generated id in a separate query
62+
var idSelect = session.Batcher.PrepareCommand(CommandType.Text, selectSql, ParametersTypes);
63+
try
64+
{
65+
BindParameters(session, idSelect, binder.Entity);
66+
var rs = session.Batcher.ExecuteReader(idSelect);
67+
try
68+
{
69+
return GetResult(session, rs, binder.Entity);
70+
}
71+
finally
72+
{
73+
session.Batcher.CloseReader(rs);
74+
}
75+
}
76+
finally
77+
{
78+
session.Batcher.CloseCommand(idSelect, null);
79+
}
6480
}
65-
finally
81+
catch (DbException sqle)
6682
{
67-
session.Batcher.CloseReader(rs);
83+
throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, sqle,
84+
"could not retrieve generated id after insert: " + persister.GetInfoString(),
85+
insertSql.Text);
6886
}
6987
}
70-
finally
71-
{
72-
session.Batcher.CloseCommand(idSelect, null);
73-
}
7488
}
75-
catch (DbException sqle)
89+
finally
7690
{
77-
throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, sqle,
78-
"could not retrieve generated id after insert: " + persister.GetInfoString(),
79-
insertSQL.Text);
91+
session.ConnectionManager.FlushEnding();
8092
}
8193
}
8294

0 commit comments

Comments
 (0)