Skip to content

Commit ad41519

Browse files
authored
Merge pull request #19 from rpaschoal/feature/mongodb-transaction-improvements
MongoDB transaction improvements and ambient transaction support
2 parents be9f1a8 + 851c2d5 commit ad41519

12 files changed

+277
-110
lines changed
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.1</TargetFramework>
5-
<Version>1.3.4</Version>
5+
<Version>1.3.6</Version>
66
<Authors>Rafael Carvalho</Authors>
77
<Description>Dynamic repository with many built-in data access methods for Repository Pattern implementation for .NET projects using EF6, EF Core or MongoDB.</Description>
88
<PackageProjectUrl>https://github.com/rpaschoal/DynamicRepository</PackageProjectUrl>
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="DynamicRepository" Version="1.3.5" />
13-
<PackageReference Include="MongoDB.Driver" Version="2.11.1" />
14-
<PackageReference Include="MongoDB.Driver.Core" Version="2.11.1" />
15-
</ItemGroup>
16-
17-
<ItemGroup>
18-
<ProjectReference Include="..\DynamicRepository\DynamicRepository.csproj" />
12+
<PackageReference Include="DynamicRepository" Version="1.3.6" />
13+
<PackageReference Include="MongoDB.Driver" Version="2.17.1" />
14+
<PackageReference Include="MongoDB.Driver.Core" Version="2.17.1" />
1915
</ItemGroup>
2016

2117
</Project>

DynamicRepository.MongoDB/MongoDBRepository.cs

Lines changed: 112 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using DynamicRepository.Filter;
2+
using DynamicRepository.MongoDB.Transaction;
23
using DynamicRepository.Transaction;
34
using MongoDB.Bson;
45
using MongoDB.Driver;
@@ -8,6 +9,7 @@
89
using System.Linq.Expressions;
910
using System.Threading;
1011
using System.Threading.Tasks;
12+
using System.Transactions;
1113

1214
namespace DynamicRepository.MongoDB
1315
{
@@ -57,18 +59,28 @@ namespace DynamicRepository.MongoDB
5759
/// </summary>
5860
protected string CollectionName { get; }
5961

60-
62+
private readonly object _transactionLock = new object();
6163
private MongoDBTransaction _transactionInstance;
62-
private MongoDBTransaction Transaction
63-
{
64+
private MongoDBTransaction Transaction
65+
{
6466
get
6567
{
66-
if (_transactionInstance != null && _transactionInstance.HasBeenDisposed)
68+
lock (_transactionLock)
6769
{
68-
_transactionInstance = null;
69-
}
70+
if (_transactionInstance != null && _transactionInstance.HasBeenDisposed)
71+
{
72+
_transactionInstance = null;
73+
}
7074

71-
return _transactionInstance;
75+
return _transactionInstance;
76+
}
77+
}
78+
set
79+
{
80+
lock (_transactionLock)
81+
{
82+
_transactionInstance = value;
83+
}
7284
}
7385
}
7486

@@ -144,16 +156,45 @@ protected FilterDefinition<Entity> GetIdFilter(Key id)
144156
return Builders<Entity>.Filter.Eq(_idPropertyName, id);
145157
}
146158

159+
public TransactionScope StartTransactionScope() => new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
160+
147161
public ITransaction StartTransaction()
148162
{
149-
_transactionInstance = new MongoDBTransaction(_mongoDatabase.Client);
163+
Transaction = new MongoDBTransaction(_mongoDatabase.Client);
150164

151165
return Transaction;
152166
}
153167

154168
public void RegisterTransaction(ITransaction transaction)
155169
{
156-
_transactionInstance = transaction as MongoDBTransaction;
170+
Transaction = transaction as MongoDBTransaction;
171+
}
172+
173+
private void EnlistWithCurrentTransactionScope()
174+
{
175+
if (System.Transactions.Transaction.Current != null)
176+
{
177+
var ambientTransactionId = System.Transactions.Transaction.Current.TransactionInformation.LocalIdentifier;
178+
179+
if (AmbientTransactionRegister.AmbientTransactions.ContainsKey(ambientTransactionId))
180+
{
181+
RegisterTransaction(AmbientTransactionRegister.AmbientTransactions[ambientTransactionId]);
182+
}
183+
else
184+
{
185+
StartTransaction();
186+
187+
AmbientTransactionRegister.AmbientTransactions.TryAdd(ambientTransactionId, Transaction);
188+
189+
System.Transactions.Transaction.Current.TransactionCompleted += (sender, e) => {
190+
AmbientTransactionRegister.AmbientTransactions.TryRemove(ambientTransactionId, out _);
191+
Transaction = null;
192+
};
193+
194+
var enlistment = new MongoDBTransactionScopeEnlistment(Transaction);
195+
System.Transactions.Transaction.Current.EnlistVolatile(enlistment, System.Transactions.EnlistmentOptions.None);
196+
}
197+
}
157198
}
158199

159200
/// <summary>
@@ -163,7 +204,19 @@ public void RegisterTransaction(ITransaction transaction)
163204
/// <returns>Persisted entity if found, otherwise NULL.</returns>
164205
public Entity Get(Key id)
165206
{
166-
var queriedEntity = Collection.Find(GetIdFilter(id)).FirstOrDefault();
207+
EnlistWithCurrentTransactionScope();
208+
209+
Entity queriedEntity;
210+
if (Transaction != null)
211+
{
212+
queriedEntity = Collection
213+
.Find(Transaction.Session, GetIdFilter(id))
214+
.FirstOrDefault();
215+
}
216+
else
217+
{
218+
queriedEntity = Collection.Find(GetIdFilter(id)).FirstOrDefault();
219+
}
167220

168221
return GlobalFilter != null && queriedEntity != null ? new[] { queriedEntity }.AsQueryable().FirstOrDefault(GlobalFilter) : queriedEntity;
169222
}
@@ -186,7 +239,21 @@ public Task<Entity> GetAsync(Key id)
186239
/// <returns>Persisted entity if found, otherwise NULL.</returns>
187240
public async Task<Entity> GetAsync(Key id, CancellationToken cancellationToken)
188241
{
189-
var queriedEntity = await (await Collection.FindAsync(GetIdFilter(id), cancellationToken: cancellationToken)).FirstOrDefaultAsync();
242+
EnlistWithCurrentTransactionScope();
243+
244+
Entity queriedEntity;
245+
if (Transaction != null)
246+
{
247+
queriedEntity = await (await Collection.FindAsync(Transaction.Session, GetIdFilter(id), cancellationToken: cancellationToken).ConfigureAwait(false))
248+
.FirstOrDefaultAsync()
249+
.ConfigureAwait(false);
250+
}
251+
else
252+
{
253+
queriedEntity = await (await Collection.FindAsync(GetIdFilter(id), cancellationToken: cancellationToken).ConfigureAwait(false))
254+
.FirstOrDefaultAsync()
255+
.ConfigureAwait(false);
256+
}
190257

191258
return GlobalFilter != null && queriedEntity != null ? new[] { queriedEntity }.AsQueryable().FirstOrDefault(GlobalFilter) : queriedEntity;
192259
}
@@ -197,6 +264,8 @@ public async Task<Entity> GetAsync(Key id, CancellationToken cancellationToken)
197264
/// <param name="entity">The new <see cref="Entity"/> instance to be persisted.</param>
198265
public void Insert(Entity entity)
199266
{
267+
EnlistWithCurrentTransactionScope();
268+
200269
if (Transaction != null)
201270
{
202271
Collection.InsertOne(Transaction.Session, entity);
@@ -223,6 +292,8 @@ public Task InsertAsync(Entity entity)
223292
/// <param name="cancellationToken">A token used for cancelling propagation.</param>
224293
public Task InsertAsync(Entity entity, CancellationToken cancellationToken)
225294
{
295+
EnlistWithCurrentTransactionScope();
296+
226297
return Transaction != null ?
227298
Collection.InsertOneAsync(Transaction.Session, entity, null, cancellationToken)
228299
: Collection.InsertOneAsync(entity, null, cancellationToken);
@@ -234,6 +305,8 @@ public Task InsertAsync(Entity entity, CancellationToken cancellationToken)
234305
/// <param name="entityToUpdate">The <see cref="Entity"/> instance to be updated.</param>
235306
public void Update(Entity entityToUpdate)
236307
{
308+
EnlistWithCurrentTransactionScope();
309+
237310
if (Transaction != null)
238311
{
239312
Collection.ReplaceOne(Transaction.Session, GetIdFilter(entityToUpdate), entityToUpdate);
@@ -260,6 +333,8 @@ public Task UpdateAsync(Entity entityToUpdate)
260333
/// <param name="cancellationToken">A token used for cancelling propagation.</param>
261334
public Task UpdateAsync(Entity entityToUpdate, CancellationToken cancellationToken)
262335
{
336+
EnlistWithCurrentTransactionScope();
337+
263338
return Transaction != null ?
264339
Collection.ReplaceOneAsync(Transaction.Session, GetIdFilter(entityToUpdate), entityToUpdate, cancellationToken: cancellationToken)
265340
: Collection.ReplaceOneAsync(GetIdFilter(entityToUpdate), entityToUpdate, cancellationToken: cancellationToken);
@@ -271,6 +346,8 @@ public Task UpdateAsync(Entity entityToUpdate, CancellationToken cancellationTok
271346
/// <param name="id">The primary key of the <see cref="Entity"/> to be deleted.</param>
272347
public void Delete(Key id)
273348
{
349+
EnlistWithCurrentTransactionScope();
350+
274351
if (Transaction != null)
275352
{
276353
Collection.DeleteOne(Transaction.Session, GetIdFilter(id));
@@ -297,6 +374,8 @@ public Task DeleteAsync(Key id)
297374
/// <param name="cancellationToken">A token used for cancelling propagation.</param>
298375
public Task DeleteAsync(Key id, CancellationToken cancellationToken)
299376
{
377+
EnlistWithCurrentTransactionScope();
378+
300379
return Transaction != null ?
301380
Collection.DeleteOneAsync(Transaction.Session, GetIdFilter(id), null, cancellationToken)
302381
: Collection.DeleteOneAsync(GetIdFilter(id), cancellationToken);
@@ -308,6 +387,8 @@ public Task DeleteAsync(Key id, CancellationToken cancellationToken)
308387
/// <param name="entityToDelete">The <see cref="Entity"/> instance to be deleted.</param>
309388
public void Delete(Entity entityToDelete)
310389
{
390+
EnlistWithCurrentTransactionScope();
391+
311392
if (Transaction != null)
312393
{
313394
Collection.DeleteOne(Transaction.Session, GetIdFilter(entityToDelete));
@@ -334,7 +415,16 @@ public Task DeleteAsync(Entity entityToDelete)
334415
/// <param name="cancellationToken">A token used for cancelling propagation.</param>
335416
public Task DeleteAsync(Entity entityToDelete, CancellationToken cancellationToken)
336417
{
337-
return Collection.DeleteOneAsync(GetIdFilter(entityToDelete), cancellationToken);
418+
EnlistWithCurrentTransactionScope();
419+
420+
if (Transaction != null)
421+
{
422+
return Collection.DeleteOneAsync(Transaction.Session, GetIdFilter(entityToDelete), null, cancellationToken);
423+
}
424+
else
425+
{
426+
return Collection.DeleteOneAsync(GetIdFilter(entityToDelete), cancellationToken);
427+
}
338428
}
339429

340430
/// <summary>
@@ -350,7 +440,16 @@ public IEnumerable<Entity> ListAll()
350440
/// </summary>
351441
public IQueryable<Entity> GetQueryable()
352442
{
353-
return GlobalFilter != null ? Collection.AsQueryable().Where(GlobalFilter) : Collection.AsQueryable();
443+
EnlistWithCurrentTransactionScope();
444+
445+
if (Transaction != null)
446+
{
447+
return GlobalFilter != null ? Collection.AsQueryable(Transaction.Session).Where(GlobalFilter) : Collection.AsQueryable(Transaction.Session);
448+
}
449+
else
450+
{
451+
return GlobalFilter != null ? Collection.AsQueryable().Where(GlobalFilter) : Collection.AsQueryable();
452+
}
354453
}
355454

356455
/// <summary>

DynamicRepository.MongoDB/MongoDBTransaction.cs

Lines changed: 0 additions & 39 deletions
This file was deleted.

DynamicRepository.MongoDB/Repository.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using MongoDB.Driver;
44
using System.Threading;
55
using System.Threading.Tasks;
6+
using System.Transactions;
67

78
namespace DynamicRepository.MongoDB
89
{
@@ -58,14 +59,10 @@ public Repository(IMongoDatabase mongoDatabase, string collectionName, string id
5859
InitializeProxy(builtRepository);
5960
}
6061

61-
public ITransaction StartTransaction()
62-
{
63-
return _mongoDBRepository.StartTransaction();
64-
}
62+
public TransactionScope StartTransactionScope() => _mongoDBRepository.StartTransactionScope();
6563

66-
public void RegisterTransaction(ITransaction transaction)
67-
{
68-
_mongoDBRepository.RegisterTransaction(transaction);
69-
}
64+
public ITransaction StartTransaction() => _mongoDBRepository.StartTransaction();
65+
66+
public void RegisterTransaction(ITransaction transaction) => _mongoDBRepository.RegisterTransaction(transaction);
7067
}
7168
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using DynamicRepository.Transaction;
2+
using System.Collections.Concurrent;
3+
4+
namespace DynamicRepository.MongoDB.Transaction
5+
{
6+
internal static class AmbientTransactionRegister
7+
{
8+
internal static ConcurrentDictionary<string, ITransaction> AmbientTransactions = new ConcurrentDictionary<string, ITransaction>();
9+
}
10+
}

0 commit comments

Comments
 (0)