diff --git a/src/NPoco.Abstractions/IAsyncBaseDatabase.cs b/src/NPoco.Abstractions/IAsyncBaseDatabase.cs new file mode 100644 index 00000000..be682722 --- /dev/null +++ b/src/NPoco.Abstractions/IAsyncBaseDatabase.cs @@ -0,0 +1,41 @@ +using System.Data; +using System.Threading; +using System.Threading.Tasks; + +namespace NPoco +{ + public interface IAsyncBaseDatabase : IBaseDatabase + { + /// + /// Opens the DbConnection manually + /// + Task OpenSharedConnectionAsync(CancellationToken cancellationToken = default); + + /// + /// Closes the DBConnection manually + /// + Task CloseSharedConnectionAsync(); + +#if NETSTANDARD2_1_OR_GREATER + /// + /// Manually begin a transaction. Recommended to use GetTransaction + /// + Task BeginTransactionAsync(); + + /// + /// Manually begin a transaction. Recommended to use GetTransaction + /// + Task BeginTransactionAsync(IsolationLevel isolationLevel); + + /// + /// Manually abort/rollback a transaction + /// + Task AbortTransactionAsync(); + + /// + /// Manually commit a transaction + /// + Task CompleteTransactionAsync(); +#endif + } +} \ No newline at end of file diff --git a/src/NPoco.Abstractions/IAsyncDatabase.cs b/src/NPoco.Abstractions/IAsyncDatabase.cs index b73fe72d..01962db1 100644 --- a/src/NPoco.Abstractions/IAsyncDatabase.cs +++ b/src/NPoco.Abstractions/IAsyncDatabase.cs @@ -99,7 +99,7 @@ public interface IAsyncDatabase : IAsyncQueryDatabase Task SaveAsync(T poco, CancellationToken cancellationToken = default); } - public interface IAsyncQueryDatabase : IBaseDatabase + public interface IAsyncQueryDatabase : IAsyncBaseDatabase { /// /// Fetch the only row of type T using the sql and parameters specified diff --git a/src/NPoco.Abstractions/NPoco - Backup.Abstractions.csproj b/src/NPoco.Abstractions/NPoco - Backup.Abstractions.csproj deleted file mode 100644 index e831f44a..00000000 --- a/src/NPoco.Abstractions/NPoco - Backup.Abstractions.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net461;netstandard2.0;netstandard2.1 - enable - latest - - - diff --git a/src/NPoco.Abstractions/NPoco.Abstractions.csproj b/src/NPoco.Abstractions/NPoco.Abstractions.csproj index e1be3bcf..763b3099 100644 --- a/src/NPoco.Abstractions/NPoco.Abstractions.csproj +++ b/src/NPoco.Abstractions/NPoco.Abstractions.csproj @@ -10,7 +10,6 @@ - diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index 6cec60af..c09af566 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -3,7 +3,6 @@ using NPoco.Expressions; using System; using System.Collections.Generic; -using System.Data; using System.Data.Common; using System.Linq; using System.Threading.Tasks; @@ -11,7 +10,6 @@ using NPoco.Linq; using System.Threading; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices.ComTypes; namespace NPoco { diff --git a/src/NPoco/AsyncHelper.cs b/src/NPoco/AsyncHelper.cs index 818f683b..0eced2cf 100644 --- a/src/NPoco/AsyncHelper.cs +++ b/src/NPoco/AsyncHelper.cs @@ -11,5 +11,10 @@ internal static T RunSync(this Task task) { return task.ConfigureAwait(false).GetAwaiter().GetResult(); } + + internal static void RunSync(this Task task) + { + task.ConfigureAwait(false).GetAwaiter().GetResult(); + } } } diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index fb4fdc4a..6acf1e6b 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -106,16 +106,27 @@ public void Dispose() // Open a connection (can be nested) public IDatabase OpenSharedConnection() { - OpenSharedConnectionImp(false); + OpenSharedConnectionImp(false, true).RunSync(); + return this; + } + + public async Task OpenSharedConnectionAsync(CancellationToken cancellationToken = default) + { + await OpenSharedConnectionImp(false, false, cancellationToken); return this; } private void OpenSharedConnectionInternal() { - OpenSharedConnectionImp(true); + OpenSharedConnectionImp(true, true).RunSync(); } - private void OpenSharedConnectionImp(bool isInternal) + private Task OpenSharedConnectionInternalAsync(CancellationToken cancellationToken = default) + { + return OpenSharedConnectionImp(true, false, cancellationToken); + } + + private async Task OpenSharedConnectionImp(bool isInternal, bool sync, CancellationToken cancellationToken = default) { if (_connectionPassedIn && _sharedConnection != null && _sharedConnection.State != ConnectionState.Open) throw new Exception("You must explicitly open the connection before executing anything when passing in a DbConnection to Database"); @@ -132,12 +143,24 @@ private void OpenSharedConnectionImp(bool isInternal) if (_sharedConnection.State == ConnectionState.Broken) { - _sharedConnection.Close(); + if (sync) + _sharedConnection.Close(); + else { +#if NETSTANDARD2_1_OR_GREATER + await _sharedConnection.CloseAsync(); +#else + _sharedConnection.Close(); +#endif + } } if (_sharedConnection.State == ConnectionState.Closed) { - _sharedConnection.Open(); + if (sync) + _sharedConnection.Open(); + else + await _sharedConnection.OpenAsync(cancellationToken); + _sharedConnection = OnConnectionOpenedInternal(_sharedConnection); //using (var cmd = _sharedConnection.CreateCommand()) @@ -155,23 +178,64 @@ private void CloseSharedConnectionInternal() CloseSharedConnection(); } - // Close a previously opened connection + private async Task CloseSharedConnectionInternalAsync() + { + if (ShouldCloseConnectionAutomatically && _transaction == null) + await CloseSharedConnectionAsync(); + } + public void CloseSharedConnection() + { + CloseSharedConnectionImp(true).RunSync(); + } + + public async Task CloseSharedConnectionAsync() + { + await CloseSharedConnectionImp(false); + } + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + private async Task CloseSharedConnectionImp(bool sync, CancellationToken cancellationToken = default) +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { if (KeepConnectionAlive) return; if (_transaction != null) { - _transaction.Dispose(); - _transaction = null; + if (sync) + { + _transaction.Dispose(); + } + else + { +#if NETSTANDARD2_1_OR_GREATER + await _transaction.DisposeAsync(); +#else + _transaction.Dispose(); +#endif + _transaction = null; + } } if (_sharedConnection == null) return; OnConnectionClosingInternal(_sharedConnection); - _sharedConnection.Close(); - _sharedConnection.Dispose(); + if (sync) + { + _sharedConnection.Close(); + _sharedConnection.Dispose(); + } + else + { +#if NETSTANDARD2_1_OR_GREATER + await _sharedConnection.CloseAsync(); + await _sharedConnection.DisposeAsync(); +#else + _sharedConnection.Close(); + _sharedConnection.Dispose(); +#endif + } _sharedConnection = null!; } @@ -282,14 +346,98 @@ public void BeginTransaction(IsolationLevel isolationLevel) } } +#if NETSTANDARD2_1_OR_GREATER + public Task BeginTransactionAsync() + { + return BeginTransactionAsync(_isolationLevel); + } + + public async Task BeginTransactionAsync(IsolationLevel isolationLevel) + { + if (_transaction == null) + { + TransactionCount = 0; + await OpenSharedConnectionInternalAsync(); + _transaction = await _sharedConnection.BeginTransactionAsync(isolationLevel); + OnBeginTransactionInternal(); + } + + if (_transaction != null) + { + TransactionCount++; + } + } + + private async Task OnBeginTransactionInternalAsync() + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("Created new transaction using isolation level of " + _transaction?.IsolationLevel + "."); +#endif + await OnBeginTransactionAsync(); + foreach (var interceptor in Interceptors.OfType()) + { + interceptor.OnBeginTransaction(this); + } + } + + protected virtual Task OnBeginTransactionAsync() + { + return Task.CompletedTask; + } + + public Task AbortTransactionAsync() + { + return AbortTransaction(false, false); + } + + private async Task OnAbortTransactionInternalAsync() + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("Rolled back a transaction"); +#endif + await OnAbortTransactionAsync(); + foreach (var interceptor in Interceptors.OfType()) + { + interceptor.OnAbortTransaction(this); + } + } + + protected virtual Task OnAbortTransactionAsync() + { + return Task.CompletedTask; + } + + public Task CompleteTransactionAsync() + { + return CompleteTransactionImp(false); + } + + private async Task OnCompleteTransactionInternalAsync() + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("Committed the transaction"); +#endif + await OnCompleteTransactionAsync(); + foreach (var interceptor in Interceptors.OfType()) + { + interceptor.OnCompleteTransaction(this); + } + } + + protected virtual Task OnCompleteTransactionAsync() + { + return Task.CompletedTask; + } +#endif + // Abort the entire outer most transaction scope public void AbortTransaction() { TransactionIsAborted = true; - AbortTransaction(false); + AbortTransaction(false, true).RunSync(); } - public void AbortTransaction(bool fromComplete) + private async Task AbortTransaction(bool fromComplete, bool sync, CancellationToken cancellationToken = default) { if (_transaction == null) { @@ -308,27 +456,88 @@ public void AbortTransaction(bool fromComplete) } if (TransactionIsOk()) - _transaction.Rollback(); + { + if (sync) + { + _transaction.Rollback(); + } + else + { +#if NETSTANDARD2_1_OR_GREATER + await _transaction.RollbackAsync(cancellationToken); +#else + _transaction.Rollback(); +#endif + } + } if (_transaction != null) - _transaction.Dispose(); - + { + if (sync) + { + _transaction.Dispose(); + } + else + { +#if NETSTANDARD2_1_OR_GREATER + await _transaction.DisposeAsync(); +#else + _transaction.Dispose(); +#endif + } + } _transaction = null; TransactionIsAborted = false; // You cannot continue to use a connection after a transaction has been rolled back if (_sharedConnection != null) { - _sharedConnection.Close(); - _sharedConnection.Open(); + if (sync) + { + _sharedConnection.Close(); + _sharedConnection.Open(); + } + else + { + if (sync) + { + _sharedConnection.Close(); + } + else + { +#if NETSTANDARD2_1_OR_GREATER + await _sharedConnection.CloseAsync(); +#else + _sharedConnection.Close(); +#endif + } + + await _sharedConnection.OpenAsync(); + } } - OnAbortTransactionInternal(); - CloseSharedConnectionInternal(); + if (sync) + { + OnAbortTransactionInternal(); + CloseSharedConnectionInternal(); + } + else + { +#if NETSTANDARD2_1_OR_GREATER + await OnAbortTransactionInternalAsync(); +#else + OnAbortTransactionInternal(); +#endif + await CloseSharedConnectionInternalAsync(); + } } - // Complete the transaction public void CompleteTransaction() + { + CompleteTransactionImp(true).RunSync(); + } + + private async Task CompleteTransactionImp(bool sync, CancellationToken cancellationToken = default) { if (_transaction == null) return; @@ -339,18 +548,62 @@ public void CompleteTransaction() if (TransactionIsAborted) { - AbortTransaction(true); + if (sync) + { + AbortTransaction(true, true).RunSync(); + } + else + { + await AbortTransaction(true, false); + } return; } if (TransactionIsOk()) - _transaction.Commit(); + { + if (sync) + { + _transaction.Commit(); + } + else + { +#if NETSTANDARD2_1_OR_GREATER + await _transaction.CommitAsync(); +#else + _transaction.Commit(); +#endif + } + } - _transaction?.Dispose(); + if (sync) + { + _transaction?.Dispose(); + } + else + { +#if NETSTANDARD2_1_OR_GREATER + if (_transaction != null) await _transaction.DisposeAsync(); +#else + _transaction?.Dispose(); +#endif + } + _transaction = null; - OnCompleteTransactionInternal(); - CloseSharedConnectionInternal(); + if (sync) + { + OnCompleteTransactionInternal(); + CloseSharedConnectionInternal(); + } + else + { +#if NETSTANDARD2_1_OR_GREATER + await OnCompleteTransactionInternalAsync(); +#else + OnCompleteTransactionInternal(); +#endif + await CloseSharedConnectionInternalAsync(); + } } internal bool TransactionIsAborted { get; set; } @@ -1577,7 +1830,7 @@ private async Task IsNewAsync(T poco, bool sync, CancellationToken canc { return sync ? !PocoExistsAsync(poco, true).RunSync() - : !await PocoExistsAsync(poco, false).ConfigureAwait(false); + : !await PocoExistsAsync(poco, false, cancellationToken).ConfigureAwait(false); } else {