public virtual async Task Commit(CancellationToken cancellationToken = default) { using var _ = await AsyncLock.Lock(cancellationToken).ConfigureAwait(false); if (IsClosed) { throw Stl.Fusion.Operations.Internal.Errors.OperationScopeIsAlreadyClosed(); } try { if (!IsUsed) { IsConfirmed = true; return; } Operation.CommitTime = Clock.Now; if (Operation.Command == null) { throw Stl.Fusion.Operations.Internal.Errors.OperationHasNoCommand(); } var dbContext = DbContext !; dbContext.DisableChangeTracking(); // Just to speed up things a bit var operation = await DbOperationLog.Add(dbContext, Operation, cancellationToken).ConfigureAwait(false); try { await Transaction !.CommitAsync(cancellationToken).ConfigureAwait(false); IsConfirmed = true; } catch (Exception) { // See https://docs.microsoft.com/en-us/ef/ef6/fundamentals/connection-resiliency/commit-failures try { // We need a new connection here, since the old one might be broken dbContext = DbContextFactory.CreateDbContext(); var committedOperation = await DbOperationLog.TryGet(dbContext, operation.Id, cancellationToken); if (committedOperation != null) { IsConfirmed = true; } } catch { // Intended } if (IsConfirmed != true) { throw; } } } finally { IsConfirmed ??= false; IsClosed = true; } }
public virtual async Task <IOperation?> CommitAsync(CancellationToken cancellationToken = default) { using var _ = await AsyncLock.LockAsync(cancellationToken).ConfigureAwait(false); if (IsCompleted) { throw Errors.TransactionScopeIsAlreadyClosed(); } try { if (!IsUsed) { return(null); } foreach (var dbContext in AllDbContexts) { if (!(dbContext.ChangeTracker.AutoDetectChangesEnabled || dbContext.ChangeTracker.HasChanges())) { continue; } await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } if (Command == null) { await Transaction !.CommitAsync(cancellationToken).ConfigureAwait(false); return(null); } else { var dbContext = PrimaryDbContext !.ReadWrite(); dbContext.ChangeTracker.Clear(); var operation = await DbOperationLog.AddAsync(dbContext, o => { o.StartTime = StartTime; o.CommitTime = Clock.Now; o.Command = Command; o.InvalidationData = InvalidationData; }, cancellationToken).ConfigureAwait(false); await Transaction !.CommitAsync(cancellationToken).ConfigureAwait(false); operation = await DbOperationLog.TryGetAsync(dbContext, operation.Id, cancellationToken); if (operation == null) { throw Errors.OperationCommitFailed(); } return(operation); } } finally { IsCompleted = true; } }
public DbOperationScope(IServiceProvider services) { var loggerFactory = services.GetService <ILoggerFactory>(); Log = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; Services = services; Clock = services.GetService <IMomentClock>() ?? SystemClock.Instance; DbContextFactory = services.GetRequiredService <IDbContextFactory <TDbContext> >(); DbOperationLog = services.GetRequiredService <IDbOperationLog <TDbContext> >(); AsyncLock = new AsyncLock(ReentryMode.CheckedPass); Operation = DbOperationLog.New(); CommandContext = services.GetRequiredService <CommandContext>(); }
public virtual async Task <IOperation?> CommitAsync(CancellationToken cancellationToken = default) { using var _ = await AsyncLock.LockAsync(cancellationToken).ConfigureAwait(false); if (IsCompleted) { throw Errors.TransactionScopeIsAlreadyClosed(); } try { if (!IsUsed) { return(null); } if (Command == null) { await Transaction !.CommitAsync(cancellationToken).ConfigureAwait(false); return(null); } else { var dbContext = PrimaryDbContext !; var operation = await DbOperationLog.AddAsync(dbContext, o => { o.StartTime = StartTime; o.CommitTime = Clock.Now; o.Command = Command; o.Items = Items; }, cancellationToken).ConfigureAwait(false); await Transaction !.CommitAsync(cancellationToken).ConfigureAwait(false); operation = await DbOperationLog.TryGetAsync(dbContext, operation.Id, cancellationToken); if (operation == null) { throw Errors.OperationCommitFailed(); } return(operation); } } finally { IsCompleted = true; } }
public virtual async Task CommitAsync(CancellationToken cancellationToken = default) { using var _ = await AsyncLock.LockAsync(cancellationToken).ConfigureAwait(false); if (IsClosed) { throw Stl.Fusion.Operations.Internal.Errors.OperationScopeIsAlreadyClosed(); } try { if (!IsUsed) { IsConfirmed = true; return; } Operation.CommitTime = Clock.Now; if (Operation.Command == null) { throw Stl.Fusion.Operations.Internal.Errors.OperationHasNoCommand(); } var dbContext = DbContext !; dbContext.DisableChangeTracking(); // Just to speed up things a bit var operation = await DbOperationLog.AddAsync(dbContext, Operation, cancellationToken).ConfigureAwait(false); await Transaction !.CommitAsync(cancellationToken).ConfigureAwait(false); operation = await DbOperationLog.TryGetAsync(dbContext, operation.Id, cancellationToken); if (operation == null) { throw Errors.OperationCommitFailed(); } IsConfirmed = true; } finally { IsConfirmed ??= false; IsClosed = true; } }