-
-
Save impworks/877ba85dd1b7b3e968ca4b4964eedde6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Data; | |
using System.Data.Common; | |
using System.Linq; | |
using System.Threading.Tasks; | |
using Microsoft.EntityFrameworkCore; | |
namespace TransactionHandlerTest | |
{ | |
/// <summary> | |
/// Distributed transaction helper. | |
/// </summary> | |
public class Transaction: IAsyncDisposable | |
{ | |
private Transaction(IEnumerable<DbConnection> connections) | |
{ | |
Connections = connections.ToList(); | |
Id = "t_" + Guid.NewGuid().ToString("N").Substring(0, 30); | |
} | |
private IReadOnlyList<DbConnection> Connections { get; } | |
private string Id { get; } | |
private bool? State { get; set; } | |
/// <summary> | |
/// Creates a transaction for the specified connections. | |
/// </summary> | |
public static async Task<Transaction> CreateAsync(IEnumerable<DbConnection> connections) | |
{ | |
var txn = new Transaction(connections); | |
await txn.Begin(); | |
return txn; | |
} | |
/// <summary> | |
/// Creates a new transaction for the specified EF DbContexts. | |
/// </summary> | |
public static Task<Transaction> CreateAsync(params DbContext[] contexts) | |
{ | |
return CreateAsync(contexts.Select(x => x.Database.GetDbConnection())); | |
} | |
/// <summary> | |
/// Begins the transaction. | |
/// </summary> | |
private ValueTask Begin() | |
{ | |
return SendCommandAsync($"BEGIN DISTRIBUTED TRANSACTION {Id}"); | |
} | |
/// <summary> | |
/// Commits the transaction. | |
/// </summary> | |
public async ValueTask CommitAsync() | |
{ | |
CheckState(); | |
await SendCommandAsync("COMMIT TRANSACTION"); | |
State = true; | |
} | |
/// <summary> | |
/// Discards any changes made in the transaction. | |
/// </summary> | |
public async ValueTask RollbackAsync() | |
{ | |
CheckState(); | |
await SendCommandAsync($"ROLLBACK TRANSACTION {Id}"); | |
State = false; | |
} | |
/// <summary> | |
/// Rolls the transaction back if it has not been manually rolled back or committed. | |
/// </summary> | |
public async ValueTask DisposeAsync() | |
{ | |
if (State != null) | |
return; | |
try | |
{ | |
await RollbackAsync(); | |
} | |
catch | |
{ | |
// ignore all errors | |
} | |
} | |
/// <summary> | |
/// Dispatches a command to all connections. | |
/// </summary> | |
private async ValueTask SendCommandAsync(string sql) | |
{ | |
foreach (var conn in Connections) | |
{ | |
if (conn.State != ConnectionState.Open) | |
await conn.OpenAsync(); | |
var cmd = conn.CreateCommand(); | |
cmd.CommandText = sql; | |
cmd.CommandType = CommandType.Text; | |
await cmd.ExecuteNonQueryAsync(); | |
} | |
} | |
/// <summary> | |
/// Ensures that the transaction is neither committed nor rolled back. | |
/// </summary> | |
private void CheckState() | |
{ | |
if (State == true) | |
throw new Exception("Transaction is already committed!"); | |
if (State == false) | |
throw new Exception("Transaction is already rolled back!"); | |
} | |
public override string ToString() | |
{ | |
return Id; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment