Ish*_*mas 7 .net c# entity-framework-core asp.net-core-mvc asp.net-core-webapi
我正在构建 3 层 ASP.NET Core Web API。它由数据层、业务层(核心层)和 WebAPI 层组成:
我正在努力决定如何处理数据库事务。我将 DbContext(作用域)注入到我的Data类和控制器中(我省略了业务项目,因为它根本不知道 EFCore)。因为每个请求只有一个实例DbContext,所以它Data在控制器中是同一个对象。
所以,业务逻辑正在做,应该做的事情,调用数据层中的对象。每当数据层需要将更改保存到数据库中时,它都会这样做。一切都围绕每个请求的事务进行。因此,如果出现问题……所有更改都会回滚。
这是示例控制器的方法,展示了我是如何做到的(简化的):
[HttpPut("{id}")]
public IActionResult UpdateMeeting(int id, [FromBody] MeetingDto meeting)
{
using (var transaction = _dbContext.Database.BeginTransaction())
{
if (meeting == null)
{
return BadRequest();
}
_meetingService.AddMeetingChanges(meeting);
meeting.Id = id;
_meetingService.UpdateMeeting(meeting);
}
return NoContent();
}
Run Code Online (Sandbox Code Playgroud)
一切都很好。那么问题出在哪里呢?我需要重复一遍:
using (var transaction = _dbContext.Database.BeginTransaction())
{
}
Run Code Online (Sandbox Code Playgroud)
...在每个操作中,都需要事务。
所以我在想,是否可以在中间件/管道中启动事务(我不确定术语)。简单地说 - 我想根据每个请求明确开始交易。我想隐藏在中间件中。这样,每当我将 DbContext 注入数据类时,事务就已经开始了
编辑:可能的解决方案:
创建了一个UnitOfWork类:
public class UnitOfWork
{
private readonly RequestDelegate _next;
public UnitOfWork(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, MyContext ctx)
{
using (var transaction = ctx.Database.BeginTransaction())
{
await _next(httpContext);
transaction.Commit();
}
}
}
Run Code Online (Sandbox Code Playgroud)在之后和之前注入UnitOfWork类作为中间件:UseHttpsRedirectionUseMvc
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Run(async context =>
{
context.Response.StatusCode = 500;
await context.Response.WriteAsync("An unexpected error happened. Please contact IT.");
});
});
}
app.UseHttpsRedirection();
app.UseMiddleware<UnitOfWork>();
app.UseMvc();
}
Run Code Online (Sandbox Code Playgroud)我遇到了类似的问题,并根据 @Ish Thomas 的建议和解决方案构建了一个中间件。为了防止有人发现这个问题,我想将我的中间件解决方案留在这里。
但不幸的是,我还必须使用 EF Connection Resiliency 配置EnableRetryOnFailure()。此配置与 不兼容ctx.Database.BeginTransaction()并抛出InvalidOperationException.
InvalidOperationException:配置的执行策略“SqlServerRetryingExecutionStrategy”不支持用户启动的事务。使用“DbContext.Database.CreateExecutionStrategy()”返回的执行策略将事务中的所有操作作为可重试单元执行。
services.AddDbContext<DemoContext>(
options => options.UseSqlServer(
"<connection string>",
providerOptions => providerOptions.EnableRetryOnFailure()));
Run Code Online (Sandbox Code Playgroud)
当 HTTP 谓词为 POST、PUT 或 DELETE 时,中间件创建事务。否则,它会在没有事务的情况下调用下一个中间件。如果抛出异常,则不会执行事务提交,并且会回滚在此请求中所做的更改。
public class TransactionUnitMiddleware
{
private readonly RequestDelegate next;
public TransactionUnitMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext, DemoContext context)
{
string httpVerb = httpContext.Request.Method.ToUpper();
if (httpVerb == "POST" || httpVerb == "PUT" || httpVerb == "DELETE")
{
var strategy = context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync<object, object>(null!, operation: async (dbctx, state, cancel) =>
{
// start the transaction
await using var transaction = await context.Database.BeginTransactionAsync();
// invoke next middleware
await next(httpContext);
// commit the transaction
await transaction.CommitAsync();
return null!;
}, null);
}
else
{
await next(httpContext);
}
}
}
Run Code Online (Sandbox Code Playgroud)
希望这对某人有帮助;)
| 归档时间: |
|
| 查看次数: |
2909 次 |
| 最近记录: |