using Domain.AggregateModel.AppAggregate; using Domain.AggregateModel.LinkAggregate; using Domain.SeedWork; using Infrastructure.EntityConfigurations; using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Storage; using Pomelo.EntityFrameworkCore.MySql.Infrastructure; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Infrastructure { public class AppDbContext : DbContext, IUnitOfWork { public DbSet Apps { get; set; } public DbSet Links { get; set; } private readonly IMediator _mediator; private IDbContextTransaction _currentTransaction; public bool HasActiveTransaction => _currentTransaction != null; public IDbContextTransaction GetCurrentTransaction() => _currentTransaction; public AppDbContext(DbContextOptions options) : base(options) { } public AppDbContext(DbContextOptions options, IMediator mediator) : base(options) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); System.Diagnostics.Debug.WriteLine("OrderingContext::ctor ->" + this.GetHashCode()); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new AppEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new LinkEntityTypeConfiguration()); } public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default) { // Dispatch Domain Events collection. // Choices: // A) Right BEFORE committing data (EF SaveChanges) into the DB will make a single transaction including // side effects from the domain event handlers which are using the same DbContext with "InstancePerLifetimeScope" or "scoped" lifetime // B) Right AFTER committing data (EF SaveChanges) into the DB will make multiple transactions. // You will need to handle eventual consistency and compensatory actions in case of failures in any of the Handlers. await _mediator.DispatchDomainEventsAsync(this); // After executing this line all the changes (from the Command Handler and Domain Event Handlers) // performed through the DbContext will be committed var result = await base.SaveChangesAsync(cancellationToken); return true; } public async Task BeginTransactionAsync() { if (_currentTransaction != null) return null; _currentTransaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted); return _currentTransaction; } public async Task CommitTransactionAsync(IDbContextTransaction transaction) { if (transaction == null) throw new ArgumentNullException(nameof(transaction)); if (transaction != _currentTransaction) throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current"); try { await SaveChangesAsync(); transaction.Commit(); } catch { RollbackTransaction(); throw; } finally { if (_currentTransaction != null) { _currentTransaction.Dispose(); _currentTransaction = null; } } } public void RollbackTransaction() { try { _currentTransaction?.Rollback(); } finally { if (_currentTransaction != null) { _currentTransaction.Dispose(); _currentTransaction = null; } } } } public class AppDbContextDesignFactory : IDesignTimeDbContextFactory { public AppDbContext CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder() .UseMySql( "server=localhost;user=root;password=root;database=qrcode", // For common usages, see pull request #1233. new MariaDbServerVersion(new Version(10, 5, 9)), // use MariaDbServerVersion for MariaDB mySqlOptions => mySqlOptions .CharSetBehavior(CharSetBehavior.NeverAppend) .MigrationsAssembly("QRCodeService")) // Everything from this point on is optional but helps with debugging. .EnableSensitiveDataLogging() .EnableDetailedErrors(); return new AppDbContext(optionsBuilder.Options, new NoMediator()); } class NoMediator : IMediator { public Task Publish(TNotification notification, CancellationToken cancellationToken = default(CancellationToken)) where TNotification : INotification { return Task.CompletedTask; } public Task Publish(object notification, CancellationToken cancellationToken = default) { return Task.CompletedTask; } public Task Send(IRequest request, CancellationToken cancellationToken = default(CancellationToken)) { return Task.FromResult(default(TResponse)); } public Task Send(object request, CancellationToken cancellationToken = default) { return Task.FromResult(default(object)); } } } }