NETCore中的事务中间件

程序员有二十年 2024-09-17 12:36:56

在某些涉及 HTTP 请求的业务场景中,需要在请求处理的每个步骤中将数据保存到数据库,并在某个部分失败时回滚之前的更改。本文将演示中间件如何促进事务,并在没有异常发生时隐式地将更改提交到数据库。我们将假设使用 SQL 数据库、Dapper micro ORM 和存储库模式来抽象数据访问层。

让我们从创建一个连接提供程序类开始:

public SqlConnectionProvider{private readonly IDbConnection _connection;private IDbTransaction _transaction;public SqlConnectionProvider(string connectionString) { _connection = new SqlConnection(connectionString); }public IDbConnection GetDbConnection => _connection;public IDbTransaction GetTransaction => _transaction;public IDbTransaction CreateTransaction() {if (_connection.State == ConnectionState.Closed) _connection.Open(); _transaction = _connection.BeginTransaction();return _transaction; }}

SqlConnectionProvider创建用于存储库注入的对象,并负责创建事务。SqlConnection

我们需要注册 scoped lifetime。SqlConnectionProvider

builder.Services.AddScoped(_ => new SqlConnectionProvider(Configuration.GetConnectionString("Default")));

是时候开发事务中间件了。我将继续插入所需的代码,以逐步管理交易。下面是 middleware 的定义。

public DbTransactionMiddleware{private readonly RequestDelegate _next;public DbTransactionMiddleware(RequestDelegate next) { _next = next; }public async Task Invoke(HttpContext httpContext, SqlConnectionProvider connectionProvider) { }}

上面的代码很简单。我们启动一个事务,调用后续的中间件,然后提交事务,并最终处理它。

public async Task Invoke(HttpContext httpContext, SqlConnectionProvider connectionProvider){IDbTransaction transaction = ;try { transaction = connectionProvider.CreateTransaction();await _next(httpContext); transaction.Commit(); }finally { transaction?.Dispose(); }}

假设我们有一个 todo 仓库。有必要将 注入到存储库中,并将打开的事务从中间件传递到已建立的 SQL 连接。SqlConnectionProvider

public TodoItemRepository : ITodoItemRepository{private readonly SqlConnectionProvider _connectionProvider;private readonly IDbConnection _connection;public TodoItemRepository(SqlConnectionProvider connectionProvider) { _connectionProvider = connectionProvider; _connection = connectionProvider.GetDbConnection; }public Task<int> AddTodoItemAsync(TodoItem todoItem) {const string command = "INSERT INTO TodoItems (Title, Note, TodoListId) VALUES (@Title, @Note, @TodoListId)";var parameters = new DynamicParameters(); parameters.Add("Title", todoItem.Title, DbType.String); parameters.Add("Note", todoItem.Note, DbType.String); parameters.Add("TodoListId", todoItem.TodoListId, DbType.Int32);// Passing transaction to ExecuteAsync methodreturn _connection.ExecuteAsync(command, parameters, _connectionProvider.GetTransaction); }public Task<IEnumerable<TodoItem>> GetTodoItemsAsync() {return _connection.ExecuteScalarAsync<IEnumerable<TodoItem>>("SELECT * FROM TodoItems"); }}

创建用于注册中间件的扩展方法:

public static MiddlewareExtensions{public static IApplicationBuilder UseDbTransaction(this IApplicationBuilder app) => app.UseMiddleware<DbTransactionMiddleware>();}

将中间件放在请求管道中 Authorization 中间件之后:

var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); var app = builder.Build(); app.UseAuthorization(); app.UseDbTransaction() app.MapControllers(); app.Run();

让我们进一步改进中间件实现。我们可以避免为 HTTP 请求打开事务。通常,对于请求,我们只获取数据,而在 和 中,我们修改数据。GETGETPOSTPUTDELETE

// For HTTP GET opening transaction is not required if (httpContext.Request.Method.Equals("GET", StringComparison.CurrentCultureIgnoreCase)) { await _next(httpContext); return; }

有时,每个 , 和 请求 都不需要打开事务。我们可以通过使用 attribute 来装饰 action 来更准确地发起交易。POSTPUTDELETE

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]public TransactionAttribute : Attribute{}

并使用 attribute 来装饰我们的 action:Transaction

[Transaction][HttpPost("todo-item")]public async Task<IActionResult> Post(...){ ...}

这是 transaction 中间件的最终代码:

public DbTransactionMiddleware{private readonly RequestDelegate _next;public DbTransactionMiddleware(RequestDelegate next) { _next = next; }public async Task Invoke(HttpContext httpContext, SqlConnectionProvider connectionProvider) {// For HTTP GET opening transaction is not requiredif (httpContext.Request.Method.Equals("GET", StringComparison.CurrentCultureIgnoreCase)) {await _next(httpContext);return; }// If action is not decorated with TransactionAttribute then skip opening transactionvar endpoint = httpContext.Features.Get<IEndpointFeature>()?.Endpoint;var attribute = endpoint?.Metadata.GetMetadata<TransactionAttribute>();if (attribute == ) {await _next(httpContext);return; }IDbTransaction transaction = ;try { transaction = connectionProvider.CreateTransaction();await _next(httpContext); transaction.Commit(); }finally { transaction?.Dispose(); } }}

如果您使用的是 Entity Framework,则可以按以下方式打开和提交事务:

IDbContextTransaction transaction = ;try{ transaction = await dbContext.Database.BeginTransactionAsync();await _next(httpContext);await transaction.CommitAsync();}finally{if (transaction != )await transaction.DisposeAsync();}

源代码获取:公众号回复消息【code:31915】

如果你喜欢我的文章,请给我一个赞!谢谢

0 阅读:0

程序员有二十年

简介:感谢大家的关注