Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 1339cee

Browse files
committed
Merge pull request #414 from jessemcdowell-AI/FixOracleMultiConnectionId
Fix oracle multiple connection identity bug
2 parents ec926e6 + 05a94a3 commit 1339cee

File tree

7 files changed

+390
-12
lines changed

7 files changed

+390
-12
lines changed

src/ServiceStack.OrmLite.Oracle.Tests/DateTimeColumnTest.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,10 @@ public void Can_store_and_retrieve_DateTime_Value()
5050

5151
db.Save(obj);
5252

53-
var id = (int)db.LastInsertId();
54-
var target = db.SingleById<Analyze>(id);
53+
var target = db.SingleById<Analyze>(obj.Id);
5554

5655
Assert.IsNotNull(target);
57-
Assert.AreEqual(id, target.Id);
56+
Assert.AreEqual(obj.Id, target.Id);
5857
Assert.AreEqual(obj.Date.ToString("yyyy-MM-dd HH:mm:ss"), target.Date.ToString("yyyy-MM-dd HH:mm:ss"));
5958
Assert.AreEqual(obj.Url, target.Url);
6059
}

src/ServiceStack.OrmLite.Oracle.Tests/ServiceStack.OrmLite.Oracle.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@
156156
<Compile Include="..\..\tests\ServiceStack.OrmLite.Tests\MockAllApiTests.cs">
157157
<Link>MockAllApiTests.cs</Link>
158158
</Compile>
159+
<Compile Include="..\..\tests\ServiceStack.OrmLite.Tests\MultipleConnectionIdTests.cs">
160+
<Link>MultipleConnectionIdTests.cs</Link>
161+
</Compile>
159162
<Compile Include="..\..\tests\ServiceStack.OrmLite.Tests\OrmLiteBasicPersistenceProviderTests.cs">
160163
<Link>OrmLiteBasicPersistenceProviderTests.cs</Link>
161164
</Compile>

src/ServiceStack.OrmLite.Oracle/OracleOrmLiteDialectProvider.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ public static OracleOrmLiteDialectProvider Instance
3636
get { return _instance ?? (_instance = new OracleOrmLiteDialectProvider()); }
3737
}
3838

39-
internal long LastInsertId { get; set; }
4039
protected bool CompactGuid;
4140

4241
internal const string StringGuidDefinition = "VARCHAR2(37)";
@@ -148,13 +147,24 @@ public override IDbConnection CreateConnection(string connectionString, Dictiona
148147

149148
public override long GetLastInsertId(IDbCommand dbCmd)
150149
{
151-
return LastInsertId;
150+
throw new NotSupportedException();
152151
}
153152

154153
public override long InsertAndGetLastInsertId<T>(IDbCommand dbCmd)
155154
{
156155
dbCmd.ExecuteScalar();
157-
return GetLastInsertId(dbCmd);
156+
157+
var modelDef = GetModel(typeof(T));
158+
159+
var primaryKey = modelDef.PrimaryKey;
160+
if (primaryKey == null)
161+
return 0;
162+
163+
var identityParameter = (DbParameter)dbCmd.Parameters[this.GetParam(SanitizeFieldNameForParamName(primaryKey.FieldName))];
164+
if (identityParameter == null)
165+
return 0;
166+
167+
return Convert.ToInt64(identityParameter.Value);
158168
}
159169

160170
public override void SetDbValue(FieldDefinition fieldDef, IDataReader reader, int colIndex, object instance)
@@ -897,13 +907,11 @@ private object GetNextValue(IDbConnection connection, IDbTransaction transaction
897907
Object retObj;
898908
if (long.TryParse(value.ToString(), out nv))
899909
{
900-
LastInsertId = nv;
901-
retObj = LastInsertId;
910+
retObj = nv;
902911
}
903912
else
904913
{
905-
LastInsertId = 0;
906-
retObj = value;
914+
retObj = 0;
907915
}
908916
return retObj;
909917
}
@@ -913,7 +921,6 @@ private object GetNextValue(IDbConnection connection, IDbTransaction transaction
913921
dbCmd.Transaction = transaction;
914922
dbCmd.CommandText = string.Format("SELECT {0}.NEXTVAL FROM dual", Quote(sequence));
915923
var result = dbCmd.LongScalar();
916-
LastInsertId = result;
917924
return result;
918925
}
919926
}

src/ServiceStack.OrmLite/OrmLiteWriteConnectionExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Data;
5+
using ServiceStack.Data;
56

67
namespace ServiceStack.OrmLite
78
{
@@ -281,7 +282,7 @@ public static int DeleteById<T>(this IDbConnection dbConn, object id)
281282

282283
/// <summary>
283284
/// Delete 1 row by the PrimaryKey where the rowVersion matches the optimistic concurrency field.
284-
/// Will throw <exception cref="RowModifiedException">RowModefiedExeption</exception> if the
285+
/// Will throw <exception cref="OptimisticConcurrencyException">RowModefiedExeption</exception> if the
285286
/// row does not exist or has a different row version.
286287
/// E.g: <para>db.DeleteById&lt;Person&gt;(1)</para>
287288
/// </summary>
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Data;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using NUnit.Framework;
7+
using ServiceStack.DataAnnotations;
8+
9+
namespace ServiceStack.OrmLite.Tests
10+
{
11+
[TestFixture]
12+
public class MultipleConnectionIdTests : OrmLiteTestBase
13+
{
14+
private int _waitingThreadCount;
15+
private int _waitingThreadsReleasedCounter;
16+
17+
[SetUp]
18+
public void SetUp()
19+
{
20+
using (var db = OpenDbConnection())
21+
{
22+
db.DropAndCreateTable<MultipleConnection>();
23+
}
24+
}
25+
26+
[Test]
27+
public void TwoSimultaneousInsertsGetDifferentIds()
28+
{
29+
var dataArray = new[]
30+
{
31+
new MultipleConnection {Data = "one"},
32+
new MultipleConnection {Data = "two"}
33+
};
34+
35+
var originalExecFilter = OrmLiteConfig.ExecFilter;
36+
try
37+
{
38+
OrmLiteConfig.ExecFilter = new PostExecuteActionExecFilter(originalExecFilter, cmd => PauseForOtherThreadsAfterInserts(cmd, 2));
39+
40+
Parallel.ForEach(dataArray, data =>
41+
{
42+
using (var db = OpenDbConnection())
43+
{
44+
data.Id = db.Insert(new MultipleConnection {Data = data.Data}, selectIdentity: true);
45+
46+
Assert.That(data.Id, Is.Not.EqualTo(0));
47+
}
48+
});
49+
}
50+
finally
51+
{
52+
OrmLiteConfig.ExecFilter = originalExecFilter;
53+
}
54+
55+
Assert.That(dataArray[1].Id, Is.Not.EqualTo(dataArray[0].Id));
56+
}
57+
58+
private void PauseForOtherThreadsAfterInserts(IDbCommand cmd, int numberOfThreads)
59+
{
60+
if (!cmd.CommandText.StartsWith("INSERT ", StringComparison.InvariantCultureIgnoreCase))
61+
return;
62+
63+
var initialReleasedCounter = _waitingThreadsReleasedCounter;
64+
Interlocked.Increment(ref _waitingThreadCount);
65+
try
66+
{
67+
var waitUntil = DateTime.UtcNow.AddSeconds(2);
68+
while ((_waitingThreadCount < numberOfThreads) && (initialReleasedCounter == _waitingThreadsReleasedCounter))
69+
{
70+
if (DateTime.UtcNow >= waitUntil)
71+
throw new Exception("There were not enough waiting threads after timeout");
72+
Thread.Sleep(1);
73+
}
74+
75+
Interlocked.Increment(ref _waitingThreadsReleasedCounter);
76+
}
77+
finally
78+
{
79+
Interlocked.Decrement(ref _waitingThreadCount);
80+
}
81+
}
82+
83+
[Test]
84+
public void TwoSimultaneousSavesGetDifferentIds()
85+
{
86+
var dataArray = new[]
87+
{
88+
new MultipleConnection {Data = "one"},
89+
new MultipleConnection {Data = "two"}
90+
};
91+
92+
var originalExecFilter = OrmLiteConfig.ExecFilter;
93+
try
94+
{
95+
OrmLiteConfig.ExecFilter = new PostExecuteActionExecFilter(originalExecFilter, cmd => PauseForOtherThreadsAfterInserts(cmd, 2));
96+
97+
Parallel.ForEach(dataArray, data =>
98+
{
99+
using (var db = OpenDbConnection())
100+
{
101+
db.Save(data);
102+
103+
Assert.That(data.Id, Is.Not.EqualTo(0));
104+
}
105+
});
106+
}
107+
finally
108+
{
109+
OrmLiteConfig.ExecFilter = originalExecFilter;
110+
}
111+
112+
Assert.That(dataArray[1].Id, Is.Not.EqualTo(dataArray[0].Id));
113+
}
114+
115+
private class PostExecuteActionExecFilter : IOrmLiteExecFilter
116+
{
117+
private readonly IOrmLiteExecFilter _inner;
118+
private readonly Action<IDbCommand> _postExecuteAction;
119+
120+
public PostExecuteActionExecFilter(IOrmLiteExecFilter inner, Action<IDbCommand> postExecuteAction)
121+
{
122+
_inner = inner;
123+
_postExecuteAction = postExecuteAction;
124+
}
125+
126+
public SqlExpression<T> SqlExpression<T>(IDbConnection dbConn)
127+
{
128+
return _inner.SqlExpression<T>(dbConn);
129+
}
130+
131+
public IDbCommand CreateCommand(IDbConnection dbConn)
132+
{
133+
var innerCommand = _inner.CreateCommand(dbConn);
134+
return new PostExcuteActionCommand(innerCommand, _postExecuteAction);
135+
}
136+
137+
public void DisposeCommand(IDbCommand dbCmd)
138+
{
139+
_inner.DisposeCommand(dbCmd);
140+
}
141+
142+
public T Exec<T>(IDbConnection dbConn, Func<IDbCommand, T> filter)
143+
{
144+
var cmd = CreateCommand(dbConn);
145+
try
146+
{
147+
return filter(cmd);
148+
}
149+
finally
150+
{
151+
DisposeCommand(cmd);
152+
}
153+
}
154+
155+
public void Exec(IDbConnection dbConn, Action<IDbCommand> filter)
156+
{
157+
var cmd = CreateCommand(dbConn);
158+
try
159+
{
160+
filter(cmd);
161+
}
162+
finally
163+
{
164+
DisposeCommand(cmd);
165+
}
166+
}
167+
168+
public IEnumerable<T> ExecLazy<T>(IDbConnection dbConn, Func<IDbCommand, IEnumerable<T>> filter)
169+
{
170+
var cmd = CreateCommand(dbConn);
171+
try
172+
{
173+
var results = filter(cmd);
174+
175+
foreach (var item in results)
176+
{
177+
yield return item;
178+
}
179+
}
180+
finally
181+
{
182+
DisposeCommand(cmd);
183+
}
184+
}
185+
}
186+
187+
private class PostExcuteActionCommand : IDbCommand
188+
{
189+
private readonly IDbCommand _inner;
190+
private readonly Action<IDbCommand> _postExecuteAction;
191+
192+
public PostExcuteActionCommand(IDbCommand inner, Action<IDbCommand> postExecuteAction)
193+
{
194+
_inner = inner;
195+
_postExecuteAction = postExecuteAction;
196+
}
197+
198+
public void Dispose()
199+
{
200+
_inner.Dispose();
201+
}
202+
203+
public void Prepare()
204+
{
205+
_inner.Prepare();
206+
}
207+
208+
public void Cancel()
209+
{
210+
_inner.Cancel();
211+
}
212+
213+
public IDbDataParameter CreateParameter()
214+
{
215+
return _inner.CreateParameter();
216+
}
217+
218+
public int ExecuteNonQuery()
219+
{
220+
var result = _inner.ExecuteNonQuery();
221+
_postExecuteAction(this);
222+
return result;
223+
}
224+
225+
public IDataReader ExecuteReader()
226+
{
227+
var result = _inner.ExecuteReader();
228+
_postExecuteAction(this);
229+
return result;
230+
}
231+
232+
public IDataReader ExecuteReader(CommandBehavior behavior)
233+
{
234+
var result = _inner.ExecuteReader(behavior);
235+
_postExecuteAction(this);
236+
return result;
237+
}
238+
239+
public object ExecuteScalar()
240+
{
241+
var result = _inner.ExecuteScalar();
242+
_postExecuteAction(this);
243+
return result;
244+
}
245+
246+
public IDbConnection Connection
247+
{
248+
get { return _inner.Connection; }
249+
set { _inner.Connection = value; }
250+
}
251+
252+
public IDbTransaction Transaction
253+
{
254+
get { return _inner.Transaction; }
255+
set { _inner.Transaction = value; }
256+
}
257+
258+
public string CommandText
259+
{
260+
get { return _inner.CommandText; }
261+
set { _inner.CommandText = value; }
262+
}
263+
264+
public int CommandTimeout
265+
{
266+
get { return _inner.CommandTimeout; }
267+
set { _inner.CommandTimeout = value; }
268+
}
269+
270+
public CommandType CommandType
271+
{
272+
get { return _inner.CommandType; }
273+
set { _inner.CommandType = value; }
274+
}
275+
276+
public IDataParameterCollection Parameters
277+
{
278+
get { return _inner.Parameters; }
279+
}
280+
281+
public UpdateRowSource UpdatedRowSource
282+
{
283+
get { return _inner.UpdatedRowSource; }
284+
set { _inner.UpdatedRowSource = value; }
285+
}
286+
}
287+
}
288+
289+
public class MultipleConnection
290+
{
291+
[AutoIncrement]
292+
public long Id { get; set; }
293+
294+
public string Data { get; set; }
295+
}
296+
}

0 commit comments

Comments
 (0)