c# - 如何使用 EFCore3.1 在 MySql 表中进行回滚
问题描述
我正在尝试在带有 EF Core 3.1 的 C# 项目中使用 Pomelo.EntityFrameworkCore.MySql Version="3.1.2" 在表 Mysql 中实现回滚
我的代码是
private readonly MySqlDbContext _context;
...
using (var transaction = _context.BeginTransaction())
{
try
{
//Add the Pbd to database
await _mediator.Send(new CreatePbdCommand
{
Pbd = pbd
});
//Add the Pbdetails to database
await _mediator.Send(new CreatePbdDetailCommand
{
PbdDetails = pbdDetails
});
throw new ArgumentException();
await _context.Commit(transaction);
}
catch (Exception ex)
{
_context.Rollback(transaction);
}
}
我的 DbContext 是
public class MySqlDbContext : DbContext
{
public virtual DbSet<Pbd> Pbd { get; set; }
public virtual DbSet<PbdDetail> PbdDetail { get; set; }
public IDbContextTransaction BeginTransaction() => Database.BeginTransaction();
public async Task Commit(IDbContextTransaction _transaction)
{
try
{
await SaveChangesAsync();
await _transaction.CommitAsync();
}
finally
{
_transaction.Dispose();
}
}
public void Rollback(IDbContextTransaction _transaction)
{
_transaction.Rollback();
_transaction.Dispose();
}
....
使用 Pomelo 提供程序,回滚不起作用,数据在数据库中是持久的,如果我将提供程序更改为 SqlServer ,回滚工作。
我需要什么才能用柚子做这个工作?
解决方案
Laurent Meyer,(来自 Pomelo GitHub 存储库)指出我的问题是什么。
明确使用我自己的 MySqlDbContext 实例。此实例不是存储库将使用的实例。
我在这里放了一个按预期工作的简单概念验证示例程序,Laurent Meyer 为证明回滚工作正确而做了该示例程序。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class Pbd
{
public int PbdId { get; set; }
public string Name { get; set; }
}
public class PbdDetail
{
public int PbdId { get; set; }
public string Status { get; set; }
}
public class MySqlDbContext : DbContext
{
private static int _contextCount = 0;
public static int ContextCount => _contextCount;
public virtual DbSet<Pbd> Pbd { get; set; }
public virtual DbSet<PbdDetail> PbdDetail { get; set; }
public MySqlDbContext()
{
Interlocked.Increment(ref _contextCount);
}
public IDbContextTransaction BeginTransaction() => Database.BeginTransaction();
public async Task Commit(IDbContextTransaction _transaction)
{
try
{
await SaveChangesAsync();
await _transaction.CommitAsync();
}
finally
{
_transaction.Dispose();
}
}
public void Rollback(IDbContextTransaction transaction)
{
transaction.Rollback();
transaction.Dispose();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseMySql(
"server=127.0.0.1;port=3306;user=root;password=;database=Issue1134_01",
b => b.ServerVersion("8.0.20-mysql"))
.UseLoggerFactory(
LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PbdDetail>()
.HasKey(e => e.PbdId);
}
}
public interface IRepository<T>
{
Task<T> AddAsync(T entity);
Task<List<T>> AddRangeAsync(List<T> entities);
}
public class Repository<TEntity> : IRepository<TEntity>
where TEntity : class, new()
{
private readonly MySqlDbContext _context;
protected readonly DbSet<TEntity> Entity;
public Repository(MySqlDbContext context)
{
_context = context;
Entity = context.Set<TEntity>();
}
public async Task<TEntity> AddAsync(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(AddAsync)} entity must not be null");
}
try
{
await _context.AddAsync(entity);
await _context.SaveChangesAsync();
return entity;
}
catch (Exception)
{
throw new Exception($"{nameof(entity)} could not be saved");
}
}
public async Task<List<TEntity>> AddRangeAsync(List<TEntity> entities)
{
if (entities == null)
{
throw new ArgumentNullException($"{nameof(AddRangeAsync)} entities must not be null");
}
try
{
await _context.AddRangeAsync(entities);
await _context.SaveChangesAsync();
return entities;
}
catch (Exception)
{
throw new Exception($"{nameof(entities)} could not be updated");
}
}
}
public class PbdRepository : Repository<Pbd>
{
public PbdRepository(MySqlDbContext context)
: base(context)
{
}
}
public class PbdDetailRepository : Repository<PbdDetail>
{
public PbdDetailRepository(MySqlDbContext context)
: base(context)
{
}
}
public class CreatePbdCommand : IRequest<Pbd>
{
public Pbd Pbd { get; set; }
}
public class CreatePbdDetailCommand : IRequest<List<PbdDetail>>
{
public List<PbdDetail> PbdDetails { get; set; }
}
public class CreatePbdCommandHandler : IRequestHandler<CreatePbdCommand, Pbd>
{
private readonly IRepository<Pbd> _repository;
public CreatePbdCommandHandler(IRepository<Pbd> repository) => _repository = repository;
public async Task<Pbd> Handle(CreatePbdCommand request, CancellationToken cancellationToken)
{
return await _repository.AddAsync(request.Pbd);
}
}
public class CreatePbdDetailCommandHandler : IRequestHandler<CreatePbdDetailCommand, List<PbdDetail>>
{
private readonly IRepository<PbdDetail> _repository;
public CreatePbdDetailCommandHandler(IRepository<PbdDetail> repository) => _repository = repository;
public async Task<List<PbdDetail>> Handle(CreatePbdDetailCommand request, CancellationToken cancellationToken)
{
return await _repository.AddRangeAsync(request.PbdDetails);
}
}
internal static class Program
{
private static async Task Main()
{
var serviceProvider = new ServiceCollection()
.AddScoped<MySqlDbContext>()
.AddScoped<IRepository<Pbd>, PbdRepository>()
.AddScoped<IRepository<PbdDetail>, PbdDetailRepository>()
.AddScoped<IRequestHandler<CreatePbdCommand, Pbd>, CreatePbdCommandHandler>()
.AddScoped<IRequestHandler<CreatePbdDetailCommand, List<PbdDetail>>, CreatePbdDetailCommandHandler>()
.BuildServiceProvider();
using (var serviceScope = serviceProvider.CreateScope())
{
var mediator = new Mediator(t => serviceScope.ServiceProvider.GetService(t));
var pbd = new Pbd {Name = "First Pdb"};
var pbdDetails = new List<PbdDetail>()
{
new PbdDetail {Status = "New"},
new PbdDetail {Status = "Old"}
};
// Uses the service provider in scope to get the same MySqlDbContext instance as the repositories.
using (var context = serviceScope.ServiceProvider.GetService<MySqlDbContext>())
{
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
using (var transaction = context.BeginTransaction())
{
try
{
//Add the Pbd to database
await mediator.Send(
new CreatePbdCommand
{
Pbd = pbd
});
//Add the Pbdetails to database
await mediator.Send(
new CreatePbdDetailCommand
{
PbdDetails = pbdDetails
});
throw new InvalidOperationException("Let's trigger the transactions rollback.");
//await context.Commit(transaction);
}
catch (Exception)
{
context.Rollback(transaction);
}
// A total of only two contexts should have been created up until now.
Debug.Assert(MySqlDbContext.ContextCount == 1);
}
}
}
// New scope to create new context.
using (var serviceScope = serviceProvider.CreateScope())
{
// Create a new context to ensure, that EF Core does not return not yet committed entities to us.
using (var context = serviceScope.ServiceProvider.GetService<MySqlDbContext>())
{
var pdbs = context.Pbd.ToList();
var pdbDetails = context.PbdDetail.ToList();
// A total of only two contexts should have been created up until now.
Debug.Assert(MySqlDbContext.ContextCount == 2);
// No entities should have been committed.
Debug.Assert(pdbs.Count == 0);
Debug.Assert(pdbDetails.Count == 0);
}
}
}
}
}
推荐阅读
- javascript - React.js 向子组件添加组件
- r - 如何阻止 Ubuntu 杀死一个长时间的程序
- node.js - Jest 模拟子模块功能
- java - Mapstruct:如何映射A扩展SingleValue
到 T 和 A 扩展 SingleValue 到 B 扩展 SingleValue 使用相同的映射器? - php - 如何让分页保留搜索参数
- python - Pandas concat 将 Int 更改为 Object(无空值)
- java - AudioTrack WRITE_BLOCKING 与 WRITE_NON_BLOCKING
- xml - Xls 子字符串字符串长度开始于
- cython - 包装一个将原始指针作为参数的 C++ 函数
- php - Laravel 字符串验证以允许空字符串