Skip to content

Commit 5a539a2

Browse files
author
Maksim Sustretov
committed
NH-3912 Batch operations with the same IBatcher instance fail on expected rows count after single failed operation.
1 parent 83625c9 commit 5a539a2

File tree

4 files changed

+145
-15
lines changed

4 files changed

+145
-15
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.NHSpecificTest.NH3912
4+
{
5+
class BatcherLovingEntity
6+
{
7+
public virtual Guid Id { get; set; }
8+
public virtual string Name { get; set; }
9+
}
10+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System;
2+
using System.Linq;
3+
using NHibernate.AdoNet;
4+
using NHibernate.Cfg;
5+
using NHibernate.Cfg.MappingSchema;
6+
using NHibernate.Linq;
7+
using NHibernate.Mapping.ByCode;
8+
using NUnit.Framework;
9+
10+
namespace NHibernate.Test.NHSpecificTest.NH3912
11+
{
12+
/// <summary>
13+
/// Fixture using 'by code' mappings
14+
/// </summary>
15+
/// <remarks>
16+
/// This fixture is identical to <see cref="BatcherLovingEntity" /> except the <see cref="BatcherLovingEntity" /> mapping is performed
17+
/// by code in the GetMappings method, and does not require the <c>Mappings.hbm.xml</c> file. Use this approach
18+
/// if you prefer.
19+
/// </remarks>
20+
public class ReusableBatcherFixture : TestCaseMappingByCode
21+
{
22+
protected override HbmMapping GetMappings()
23+
{
24+
var mapper = new ModelMapper();
25+
mapper.Class<BatcherLovingEntity>(rc =>
26+
{
27+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
28+
rc.Property(x => x.Name, m => m.Unique(true));
29+
});
30+
31+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
32+
}
33+
34+
protected override void Configure(Configuration configuration)
35+
{
36+
base.Configure(configuration);
37+
configuration.SetProperty(Cfg.Environment.BatchStrategy,
38+
typeof(OracleDataClientBatchingBatcherFactory).AssemblyQualifiedName);
39+
}
40+
41+
protected override void OnSetUp()
42+
{
43+
using (ISession session = OpenSession())
44+
using (ITransaction transaction = session.BeginTransaction())
45+
{
46+
var e1 = new BatcherLovingEntity { Name = "Bob" };
47+
session.Save(e1);
48+
49+
var e2 = new BatcherLovingEntity { Name = "Sally" };
50+
session.Save(e2);
51+
52+
session.Flush();
53+
transaction.Commit();
54+
}
55+
}
56+
57+
protected override void OnTearDown()
58+
{
59+
using (ISession session = OpenSession())
60+
using (ITransaction transaction = session.BeginTransaction())
61+
{
62+
session.Delete("from System.Object");
63+
64+
session.Flush();
65+
transaction.Commit();
66+
}
67+
}
68+
69+
/// <summary> Batch operations with the same IBatcher instance fail on expect rows count after single failed operation. </summary>
70+
[Test]
71+
public void Test_Batcher_Is_Reusable_After_Failed_Operation()
72+
{
73+
using (ISession session = OpenSession())
74+
{
75+
try
76+
{
77+
using (session.BeginTransaction())
78+
{
79+
var valid = new BatcherLovingEntity { Name = "Bill" };
80+
session.Save(valid);
81+
82+
Assert.That(() => session.Query<BatcherLovingEntity>().Count(x => x.Name == "Bob"), Is.EqualTo(1));
83+
var bob = new BatcherLovingEntity { Name = "Bob" };
84+
session.Save(bob);
85+
86+
// Should fail on unique constraint violation
87+
// Expected behavior
88+
session.Flush();
89+
}
90+
}
91+
catch (Exception)
92+
{
93+
// Opening next transaction in the same session after rollback
94+
// to log the problem, for instance.
95+
// Executing in the same session with the same instance of IBatcher
96+
using (session.BeginTransaction())
97+
{
98+
// Inserting any valid entity will fail on expected rows count assert in batcher
99+
var e1 = new BatcherLovingEntity { Name = "Robert (because Bob already exists)" };
100+
session.Save(e1);
101+
// Batch update returned unexpected row count from update; actual row count: 1; expected: 2
102+
session.Flush();
103+
}
104+
}
105+
}
106+
}
107+
}
108+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,8 @@
723723
<Compile Include="NHSpecificTest\BagWithLazyExtraAndFilter\Fixture.cs" />
724724
<Compile Include="Linq\ByMethod\DistinctTests.cs" />
725725
<Compile Include="Component\Basic\ComponentWithUniqueConstraintTests.cs" />
726+
<Compile Include="NHSpecificTest\NH3912\BatcherLovingEntity.cs" />
727+
<Compile Include="NHSpecificTest\NH3912\ReusableBatcherFixture.cs" />
726728
<Compile Include="NHSpecificTest\NH3414\Entity.cs" />
727729
<Compile Include="NHSpecificTest\NH3414\FixtureByCode.cs" />
728730
<Compile Include="NHSpecificTest\NH2218\Fixture.cs" />

src/NHibernate/AdoNet/OracleDataClientBatchingBatcher.cs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -125,21 +125,31 @@ protected override void DoExecuteBatch(IDbCommand ps)
125125
// this value is not a part of the ADO.NET API.
126126
// It's and ODP implementation, so it is being set by reflection
127127
SetArrayBindCount(arraySize);
128-
int rowsAffected;
129-
try
130-
{
131-
rowsAffected = _currentBatch.ExecuteNonQuery();
132-
}
133-
catch (DbException e)
134-
{
135-
throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not execute batch command.");
136-
}
137-
138-
Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, rowsAffected);
139-
140-
_totalExpectedRowsAffected = 0;
141-
_currentBatch = null;
142-
_parameterValueListHashTable = null;
128+
try
129+
{
130+
int rowsAffected;
131+
try
132+
{
133+
rowsAffected = _currentBatch.ExecuteNonQuery();
134+
}
135+
catch (DbException e)
136+
{
137+
// Message from DbException could be useful
138+
var additionalMessage = string.IsNullOrEmpty(e.Message) ? "." : string.Concat(": ", e.Message);
139+
throw ADOExceptionHelper.Convert(
140+
Factory.SQLExceptionConverter, e,
141+
string.Format("could not execute batch command{0}", additionalMessage));
142+
}
143+
144+
Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, rowsAffected);
145+
}
146+
finally
147+
{
148+
// Cleaning up even if batched outcome is invalid
149+
_totalExpectedRowsAffected = 0;
150+
_currentBatch = null;
151+
_parameterValueListHashTable = null;
152+
}
143153
}
144154
}
145155

0 commit comments

Comments
 (0)