mirror of https://github.com/abpframework/abp.git
10 changed files with 374 additions and 34 deletions
@ -0,0 +1,42 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using MongoDB.Driver; |
||||
|
|
||||
|
namespace Volo.Abp.Uow.MongoDB |
||||
|
{ |
||||
|
public class MongoDbTransactionApi : ITransactionApi, ISupportsRollback |
||||
|
{ |
||||
|
public IClientSessionHandle SessionHandle { get; } |
||||
|
|
||||
|
public MongoDbTransactionApi(IClientSessionHandle sessionHandle) |
||||
|
{ |
||||
|
SessionHandle = sessionHandle; |
||||
|
} |
||||
|
|
||||
|
public async Task CommitAsync() |
||||
|
{ |
||||
|
await SessionHandle.CommitTransactionAsync(); |
||||
|
} |
||||
|
|
||||
|
protected void Commit() |
||||
|
{ |
||||
|
SessionHandle.CommitTransaction(); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
SessionHandle.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public void Rollback() |
||||
|
{ |
||||
|
SessionHandle.AbortTransaction(); |
||||
|
} |
||||
|
|
||||
|
public async Task RollbackAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
await SessionHandle.AbortTransactionAsync(cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using MongoDB.Driver; |
||||
|
|
||||
|
namespace Volo.Abp.MongoDB |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// TODO It can be removed, when Mongo2Go solves this issue : https://github.com/Mongo2Go/Mongo2Go/issues/89
|
||||
|
/// </summary>
|
||||
|
internal static class MongoClientExtension |
||||
|
{ |
||||
|
private static readonly TimeSpan InitialDelay = TimeSpan.FromMilliseconds(500); |
||||
|
private static readonly TimeSpan MaxDelay = TimeSpan.FromSeconds(5000); |
||||
|
|
||||
|
|
||||
|
public static void EnsureReplicationSetReady(this IMongoClient mongoClient) |
||||
|
{ |
||||
|
var delay = InitialDelay; |
||||
|
var database = mongoClient.GetDatabase("__dummy-db"); |
||||
|
try |
||||
|
{ |
||||
|
while (true) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
_ = database.GetCollection<DummyEntry>("__dummy"); |
||||
|
database.DropCollection("__dummy"); |
||||
|
|
||||
|
var session = mongoClient.StartSession(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
session.StartTransaction(); |
||||
|
session.AbortTransaction(); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
session.Dispose(); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
catch (NotSupportedException) { } |
||||
|
|
||||
|
Thread.Sleep(delay); |
||||
|
delay = Min(Double(delay), MaxDelay); |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
mongoClient.DropDatabase("__dummy-db"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static TimeSpan Min(TimeSpan left, TimeSpan right) |
||||
|
{ |
||||
|
return new TimeSpan(Math.Min(left.Ticks, right.Ticks)); |
||||
|
} |
||||
|
|
||||
|
private static TimeSpan Double(TimeSpan timeSpan) |
||||
|
{ |
||||
|
long ticks; |
||||
|
try |
||||
|
{ |
||||
|
ticks = checked(timeSpan.Ticks * 2); |
||||
|
} |
||||
|
catch (OverflowException) |
||||
|
{ |
||||
|
if (timeSpan.Ticks >= 0) |
||||
|
{ |
||||
|
return TimeSpan.MaxValue; |
||||
|
} |
||||
|
|
||||
|
return TimeSpan.MinValue; |
||||
|
} |
||||
|
|
||||
|
return new TimeSpan(ticks); |
||||
|
} |
||||
|
|
||||
|
private sealed class DummyEntry |
||||
|
{ |
||||
|
public int Id { get; set; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
using Volo.Abp.TestApp.Domain; |
||||
|
using Volo.Abp.TestApp.Testing; |
||||
|
using Volo.Abp.Uow; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.MongoDB.Transactions |
||||
|
{ |
||||
|
public class Transaction_Tests : TestAppTestBase<AbpMongoDbTestModule> |
||||
|
{ |
||||
|
|
||||
|
private readonly IBasicRepository<Person, Guid> _personRepository; |
||||
|
private readonly IBasicRepository<City, Guid> _cityRepository; |
||||
|
private readonly IUnitOfWorkManager _unitOfWorkManager; |
||||
|
|
||||
|
public Transaction_Tests() |
||||
|
{ |
||||
|
_personRepository = ServiceProvider.GetRequiredService<IBasicRepository<Person, Guid>>(); |
||||
|
_cityRepository = ServiceProvider.GetRequiredService<IBasicRepository<City, Guid>>(); |
||||
|
_unitOfWorkManager = ServiceProvider.GetRequiredService<IUnitOfWorkManager>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Rollback_Transaction_When_An_Exception_Is_Thrown() |
||||
|
{ |
||||
|
var personId = Guid.NewGuid(); |
||||
|
const string exceptionMessage = "thrown to rollback the transaction!"; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await WithUnitOfWorkAsync(new AbpUnitOfWorkOptions {IsTransactional = true}, async () => |
||||
|
{ |
||||
|
await _personRepository.InsertAsync(new Person(personId, "Adam", 42)); |
||||
|
throw new Exception(exceptionMessage); |
||||
|
}); |
||||
|
} |
||||
|
catch (Exception e) when (e.Message == exceptionMessage) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
var person = await _personRepository.FindAsync(personId); |
||||
|
person.ShouldBeNull(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Rollback_Transaction_Manually() |
||||
|
{ |
||||
|
var personId = Guid.NewGuid(); |
||||
|
|
||||
|
await WithUnitOfWorkAsync(new AbpUnitOfWorkOptions {IsTransactional = true}, async () => |
||||
|
{ |
||||
|
_unitOfWorkManager.Current.ShouldNotBeNull(); |
||||
|
|
||||
|
await _personRepository.InsertAsync(new Person(personId, "Adam", 42)); |
||||
|
|
||||
|
await _unitOfWorkManager.Current.RollbackAsync(); |
||||
|
}); |
||||
|
|
||||
|
var person = await _personRepository.FindAsync(personId); |
||||
|
person.ShouldBeNull(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Rollback_Transaction_Manually_With_Double_DbContext_Transaction() |
||||
|
{ |
||||
|
var personId = Guid.NewGuid(); |
||||
|
var cityId = Guid.NewGuid(); |
||||
|
|
||||
|
using (var scope = ServiceProvider.CreateScope()) |
||||
|
{ |
||||
|
var uowManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>(); |
||||
|
|
||||
|
using (uowManager.Begin(new AbpUnitOfWorkOptions {IsTransactional = true})) |
||||
|
{ |
||||
|
_unitOfWorkManager.Current.ShouldNotBeNull(); |
||||
|
|
||||
|
await _personRepository.InsertAsync(new Person(personId, "Adam", 42)); |
||||
|
await _cityRepository.InsertAsync(new City(cityId, cityId.ToString())); |
||||
|
|
||||
|
await _unitOfWorkManager.Current.SaveChangesAsync(); |
||||
|
|
||||
|
//Will automatically rollback since not called the Complete!
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
(await _personRepository.FindAsync(personId)).ShouldBeNull(); |
||||
|
(await _cityRepository.FindAsync(cityId)).ShouldBeNull(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue