Как сделать откат в MySql таблицах с EFCore3.1 - PullRequest
0 голосов
/ 01 августа 2020

Я пытаюсь реализовать откат в таблицах Mysql с помощью Pomelo.EntityFrameworkCore. MySql Version = "3.1.2", в моем C# проекте с EF Core 3.1

Мой код

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, откат работает.

Что мне нужно, чтобы эта работа работала с помело?

1 Ответ

0 голосов
/ 03 августа 2020

Laurent Meyer (из репозитория Pomelo GitHub), укажите мне, в чем была моя проблема.

Явно использует мой собственный экземпляр MySqlDbContext. Этот экземпляр НЕ является экземпляром, который будут использовать репозитории.

Я представляю здесь пример программы Simple-proof-of-concept, которая работает, как ожидалось, которую сделал Лоран Мейер, чтобы доказать правильность работы отката.

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);
                }
            }
        }
    }
}
...