diff --git a/NHibernate5.SqlAzure.Tests/NHibernate5.SqlAzure.Tests.csproj b/NHibernate5.SqlAzure.Tests/NHibernate5.SqlAzure.Tests.csproj index 0403260..2420b58 100644 --- a/NHibernate5.SqlAzure.Tests/NHibernate5.SqlAzure.Tests.csproj +++ b/NHibernate5.SqlAzure.Tests/NHibernate5.SqlAzure.Tests.csproj @@ -68,8 +68,9 @@ ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\packages\NHibernate.5.2.3\lib\net461\NHibernate.dll + + ..\packages\NHibernate.5.3.9\lib\net461\NHibernate.dll + True ..\packages\NUnit.2.6.2\lib\nunit.framework.dll diff --git a/NHibernate5.SqlAzure.Tests/packages.config b/NHibernate5.SqlAzure.Tests/packages.config index e6448bf..509882f 100644 --- a/NHibernate5.SqlAzure.Tests/packages.config +++ b/NHibernate5.SqlAzure.Tests/packages.config @@ -10,7 +10,7 @@ - + diff --git a/NHibernate5.SqlAzure/NHibernate5.SqlAzure.csproj b/NHibernate5.SqlAzure/NHibernate5.SqlAzure.csproj index 5333aeb..bfaa0e9 100644 --- a/NHibernate5.SqlAzure/NHibernate5.SqlAzure.csproj +++ b/NHibernate5.SqlAzure/NHibernate5.SqlAzure.csproj @@ -44,8 +44,9 @@ ..\packages\EnterpriseLibrary.TransientFaultHandling.Data.6.0.1304.1\lib\NET45\Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling.Data.dll - - ..\packages\NHibernate.5.2.3\lib\net461\NHibernate.dll + + ..\packages\NHibernate.5.3.9\lib\net461\NHibernate.dll + True ..\packages\Remotion.Linq.2.2.0\lib\net45\Remotion.Linq.dll diff --git a/NHibernate5.SqlAzure/ReliableSqlClientBatchingBatcher.cs b/NHibernate5.SqlAzure/ReliableSqlClientBatchingBatcher.cs index 5c3b2bb..222b71c 100644 --- a/NHibernate5.SqlAzure/ReliableSqlClientBatchingBatcher.cs +++ b/NHibernate5.SqlAzure/ReliableSqlClientBatchingBatcher.cs @@ -5,6 +5,8 @@ using System.Data.Common; using System.Reflection; using System.Text; +using System.Threading; +using System.Threading.Tasks; using NHibernate.AdoNet; using NHibernate.AdoNet.Util; using NHibernate.Exceptions; @@ -94,35 +96,125 @@ public override void AddToBatch(IExpectation expectation) #endregion } - // Need this method call in this class rather than the base class to ensure Prepare is called... if only it was virtual :( - protected void ExecuteBatch(IDbCommand ps) + public override Task AddToBatchAsync(IExpectation expectation, CancellationToken cancellationToken) { #region NHibernate code - Log.Debug("Executing batch"); - CheckReaders(); - Prepare(_currentBatch.BatchCommand); - if (Factory.Settings.SqlStatementLogger.IsDebugEnabled) + if (cancellationToken.IsCancellationRequested) { - Factory.Settings.SqlStatementLogger.LogBatchCommand(_currentBatchCommandsLog.ToString()); - _currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:"); + return Task.FromCanceled(cancellationToken); } - - int rowsAffected; try { - rowsAffected = _currentBatch.ExecuteNonQuery(); + _totalExpectedRowsAffected += expectation.ExpectedRowCount; + var batchUpdate = CurrentCommand; + Driver.AdjustCommand(batchUpdate); + string lineWithParameters = null; + var sqlStatementLogger = Factory.Settings.SqlStatementLogger; + if (sqlStatementLogger.IsDebugEnabled || Log.IsDebugEnabled()) + { + lineWithParameters = sqlStatementLogger.GetCommandLineWithParameters(batchUpdate); + var formatStyle = sqlStatementLogger.DetermineActualStyle(FormatStyle.Basic); + lineWithParameters = formatStyle.Formatter.Format(lineWithParameters); + _currentBatchCommandsLog.Append("command ") + .Append(_currentBatch.CountOfCommands) + .Append(":") + .AppendLine(lineWithParameters); + } + if (Log.IsDebugEnabled()) + { + Log.Debug("Adding to batch:{0}", lineWithParameters); + } + #endregion + + _currentBatch.Append((System.Data.SqlClient.SqlCommand)(ReliableSqlCommand)batchUpdate); + + #region NHibernate code + if (_currentBatch.CountOfCommands >= _batchSize) + { + return ExecuteBatchWithTimingAsync(batchUpdate, cancellationToken); + } + return Task.CompletedTask; } - catch (DbException e) + catch (Exception ex) { - throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not execute batch command."); + return Task.FromException(ex); } + #endregion + } + + // Need this method call in this class rather than the base class to ensure Prepare is called... if only it was virtual :( + protected void ExecuteBatch(DbCommand ps) + { + try + { + Log.Debug("Executing batch"); + CheckReaders(); + Prepare(_currentBatch.BatchCommand); + if (Factory.Settings.SqlStatementLogger.IsDebugEnabled) + { + Factory.Settings.SqlStatementLogger.LogBatchCommand(_currentBatchCommandsLog.ToString()); + } + int rowsAffected; + try + { + rowsAffected = _currentBatch.ExecuteNonQuery(); + } + catch (DbException e) + { + throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not execute batch command."); + } - Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, rowsAffected); + Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, rowsAffected, ps); + } + finally + { + ClearCurrentBatch(); + } + } + #region Possible Future Use if async support for retries is added (commented out) + // protected async Task ExecuteBatchAsync(DbCommand ps, CancellationToken cancellationToken) + // { + // cancellationToken.ThrowIfCancellationRequested(); + // try + // { + // Log.Debug("Executing batch"); + // await (CheckReadersAsync(cancellationToken)).ConfigureAwait(false); + // await (PrepareAsync(_currentBatch.BatchCommand, cancellationToken)).ConfigureAwait(false); + // if (Factory.Settings.SqlStatementLogger.IsDebugEnabled) + // { + // Factory.Settings.SqlStatementLogger.LogBatchCommand(_currentBatchCommandsLog.ToString()); + // } + // int rowsAffected; + // try + // { + // rowsAffected = _currentBatch.ExecuteNonQuery(); + // } + // catch (DbException e) + // { + // throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not execute batch command."); + // } + // + // Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, rowsAffected, ps); + // } + // finally + // { + // ClearCurrentBatch(); + // } + // } + #endregion + + // Copied from NHibernate base class + private void ClearCurrentBatch() + { _currentBatch.Dispose(); _totalExpectedRowsAffected = 0; _currentBatch = CreateConfiguredBatch(); - #endregion + + if (Factory.Settings.SqlStatementLogger.IsDebugEnabled) + { + _currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:"); + } } /// @@ -133,7 +225,7 @@ protected void ExecuteBatch(IDbCommand ps) /// and if one exists. It will call Prepare if the Driver /// supports preparing commands. /// - protected new void Prepare(DbCommand cmd) + private new void Prepare(DbCommand cmd) { try { @@ -154,7 +246,7 @@ protected void ExecuteBatch(IDbCommand ps) cmd.Connection = (System.Data.SqlClient.SqlConnection) sessionConnection; } - _connectionManager.Transaction.Enlist(cmd); + _connectionManager.CurrentTransaction?.Enlist(cmd); Driver.PrepareCommand(cmd); #endregion } @@ -171,5 +263,14 @@ protected override void DoExecuteBatch(DbCommand ps) var connection = (ReliableSqlDbConnection)_connectionManager.GetConnection(); ReliableAdoTransaction.ExecuteWithRetry(connection, () => ExecuteBatch(ps)); } + + protected override async Task DoExecuteBatchAsync(DbCommand ps, CancellationToken cancellationToken) + { + var connection = (ReliableSqlDbConnection) await _connectionManager.GetConnectionAsync(cancellationToken); + ReliableAdoTransaction.ExecuteWithRetry(connection, () => ExecuteBatch(ps)); + + // NOTE: To support full async, changes will need to be made all the way through to Enterprise Library code + // ReliableAdoTransaction.ExecuteWithRetry(connection, async () => await ExecuteBatchAsync(ps, cancellationToken)); + } } } \ No newline at end of file diff --git a/NHibernate5.SqlAzure/packages.config b/NHibernate5.SqlAzure/packages.config index fbe3c5d..9e6f74a 100644 --- a/NHibernate5.SqlAzure/packages.config +++ b/NHibernate5.SqlAzure/packages.config @@ -5,7 +5,7 @@ - + \ No newline at end of file