CollaboratingPlatypus / PetaPoco

Official PetaPoco, A tiny ORM-ish thing for your POCO's

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Async Transaction Management Support

dje1990 opened this issue · comments

Hi,

Please consider supporting async methods for managing transactions, e.g. when beginning transactions, committing transactions and rolling back transactions.

I am not familiar with the PetaPoco code base, and have limited knowledge of the nuances of database connections and transactions in .NET, but it would be great if someone can review the below untested pseudo code and use it to implement async transaction management in PetaPoco.

Thanks for your feedback.

E.g. untested pseudo code based on IL code:

old begin-transaction method to be replaced:

    public async Task BeginTransactionAsync(CancellationToken cancellationToken)
    {
      ++this._transactionDepth;
      if (this._transactionDepth != 1)
        return;
      await this.OpenSharedConnectionAsync(cancellationToken).ConfigureAwait(false);
      this._transaction = !this._isolationLevel.HasValue ? this._sharedConnection.BeginTransaction() : this._sharedConnection.BeginTransaction(this._isolationLevel.Value);
      this._transactionCancelled = false;
      this.OnBeginTransaction();
    }

new begin-transaction method with async transaction support:

    public async Task BeginTransactionAsync(CancellationToken cancellationToken)
    {
      ++this._transactionDepth;
      if (this._transactionDepth != 1)
        return;
      await this.OpenSharedConnectionAsync(cancellationToken).ConfigureAwait(false);

      if (this._isolationLevel.HasValue)
      {
        if (this._sharedConnection is DbConnection asyncDbConnection)
        {
          this._transaction = await asyncDbConnection.BeginTransactionAsync(this._isolationLevel.Value).ConfigureAwait(false);
        }
        else
        {
          this._transaction = this._sharedConnection.BeginTransaction(this._isolationLevel.Value);
        }
      }
      else
      {
        if (this._sharedConnection is DbConnection asyncDbConnection)
        {
          this._transaction = await asyncDbConnection.BeginTransactionAsync().ConfigureAwait(false);
        }
        else
        {
          this._transaction = this._sharedConnection.BeginTransaction();
        }
      }

      this._transactionCancelled = false;
      this.OnBeginTransaction();
    }

old non-async methods to keep

    private void CleanupTransaction()
    {
      this.OnEndTransaction();
      if (this._transactionCancelled)
        this._transaction.Rollback();
      else
        this._transaction.Commit();
      this._transaction.Dispose();
      this._transaction = (IDbTransaction) null;
      this.CloseSharedConnection();
    }

    public void AbortTransaction()
    {
      this._transactionCancelled = true;
      if (--this._transactionDepth != 0)
        return;
      this.CleanupTransaction();
    }

    public void CompleteTransaction()
    {
      if (--this._transactionDepth != 0)
        return;
      this.CleanupTransaction();
    }

new async methods to add alongside existing non-async methods

    private Task CleanupTransactionAsync()
    {
      this.OnEndTransaction();

      if (this._transactionCancelled)
      {
        if (this._transaction is DbTransaction asyncDbTransaction)
        {
          await asyncDbTransaction.RollbackAsync().ConfigureAwait(false);
        }
        else
        {
          this._transaction.Rollback();
        }
      }
      else
      {
        if (this._transaction is DbTransaction asyncDbTransaction)
        {
          await asyncDbTransaction.CommitAsync().ConfigureAwait(false);
        }
        else
        {
          this._transaction.Commit();
        }
      }

      this._transaction.Dispose();
      this._transaction = (IDbTransaction) null;
      this.CloseSharedConnection();
    }

    public Task AbortTransactionAsync()
    {
      this._transactionCancelled = true;
      if (--this._transactionDepth != 0)
        return Task.CompletedTask;
      await this.CleanupTransactionAsync().ConfigureAwait(false);
    }

    public Task CompleteTransactionAsync()
    {
      if (--this._transactionDepth != 0)
        return Task.CompletedTask;
      await this.CleanupTransactionAsync().ConfigureAwait(false);
    }

I started looking at a PR for this, but BeginTransactionAsync() is not supported by any of PetaPoco's current target frameworks (net40, net45, netstandard2.0). We'd have to add a netstandard2.1 target in order to support this.

@pleb If you're okay with this, I'll proceed.

Well, dropping any of our current targets would be a breaking change. I'm not opposed to that, but there were some other things that were considered for a future v7 that still haven't happened.

Adding a netstandard2.1 target is an easy thing, could even add a net48 target as well. I think there's no point in targeting netcoreapp3.1, since the netstandard targets cover that (and cover net5 and net6 as well).