Skip to content

Commit

Permalink
Merge pull request #56 from FrendsPlatform/issue43
Browse files Browse the repository at this point in the history
Options.NotifyAfter fix.
  • Loading branch information
RikuVirtanen authored Sep 11, 2024
2 parents 6ccdece + 21f22c5 commit 49e7c01
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 18 deletions.
4 changes: 4 additions & 0 deletions Frends.MicrosoftSQL.BulkInsert/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [2.2.0] - 2024-09-10
### Changed
- Updated Options.NotifyAfter property to be set dynamically based on the total row count, with a minimum value of 1, ensuring rowsCopied is updated correctly.

## [2.1.0] - 2024-08-26
### Changed
- Updated Newtonsoft.Json to the latest version 13.0.3.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Frends.MicrosoftSQL.BulkInsert.Definitions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Data.SqlClient;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Frends.MicrosoftSQL.BulkInsert.Tests;

Expand Down Expand Up @@ -89,7 +89,7 @@ public async Task TestBulkInsert_FireTriggers()
CommandTimeoutSeconds = 60,
FireTriggers = true,
KeepIdentity = false,
NotifyAfter = 1,
NotifyAfter = 0,
ConvertEmptyPropertyValuesToNull = false,
KeepNulls = false,
TableLock = false,
Expand Down Expand Up @@ -128,7 +128,7 @@ public async Task TestBulkInsert_KeepIdentity()
CommandTimeoutSeconds = 60,
FireTriggers = false,
KeepIdentity = true,
NotifyAfter = 1,
NotifyAfter = 0,
ConvertEmptyPropertyValuesToNull = false,
KeepNulls = false,
TableLock = false,
Expand Down Expand Up @@ -167,7 +167,7 @@ public async Task TestBulkInsert_ConvertEmptyPropertyValuesToNull()
CommandTimeoutSeconds = 60,
FireTriggers = false,
KeepIdentity = false,
NotifyAfter = 1,
NotifyAfter = 0,
ConvertEmptyPropertyValuesToNull = true,
KeepNulls = false,
TableLock = false,
Expand Down Expand Up @@ -206,7 +206,7 @@ public async Task TestBulkInsert_KeepNulls()
CommandTimeoutSeconds = 60,
FireTriggers = false,
KeepIdentity = false,
NotifyAfter = 1,
NotifyAfter = 0,
ConvertEmptyPropertyValuesToNull = false,
KeepNulls = true,
TableLock = false,
Expand Down Expand Up @@ -245,7 +245,7 @@ public async Task TestBulkInsert_TableLock()
CommandTimeoutSeconds = 60,
FireTriggers = false,
KeepIdentity = false,
NotifyAfter = 1,
NotifyAfter = 0,
ConvertEmptyPropertyValuesToNull = false,
KeepNulls = false,
TableLock = true,
Expand Down Expand Up @@ -285,7 +285,7 @@ public async Task TestBulkInsert_All()
CommandTimeoutSeconds = 60,
FireTriggers = true,
KeepIdentity = true,
NotifyAfter = 1,
NotifyAfter = 0,
ConvertEmptyPropertyValuesToNull = true,
KeepNulls = true,
TableLock = true,
Expand All @@ -303,6 +303,123 @@ public async Task TestBulkInsert_All()
}
}

[TestMethod]
public async Task TestBulkInsert_NotifyAfterZero()
{
var transactionLevels = new List<SqlTransactionIsolationLevel>() {
SqlTransactionIsolationLevel.Unspecified,
SqlTransactionIsolationLevel.Serializable,
SqlTransactionIsolationLevel.None,
SqlTransactionIsolationLevel.ReadUncommitted,
SqlTransactionIsolationLevel.ReadCommitted
};

foreach (var transactionLevel in transactionLevels)
{
Init();

var options = new Options()
{
SqlTransactionIsolationLevel = transactionLevel,
CommandTimeoutSeconds = 60,
FireTriggers = false,
KeepIdentity = false,
NotifyAfter = -1,
ConvertEmptyPropertyValuesToNull = false,
KeepNulls = true,
TableLock = false,
};

var result = await MicrosoftSQL.BulkInsert(_input, options, default);
Assert.IsTrue(result.Success);
Assert.AreEqual(0, result.Count);
Assert.AreEqual(3, GetRowCount());

await MicrosoftSQL.BulkInsert(_input, options, default);
Assert.AreEqual(6, GetRowCount());

CleanUp();
}
}

[TestMethod]
public async Task TestBulkInsert_NotifyAfterTooMuch()
{
var transactionLevels = new List<SqlTransactionIsolationLevel>() {
SqlTransactionIsolationLevel.Unspecified,
SqlTransactionIsolationLevel.Serializable,
SqlTransactionIsolationLevel.None,
SqlTransactionIsolationLevel.ReadUncommitted,
SqlTransactionIsolationLevel.ReadCommitted
};

foreach (var transactionLevel in transactionLevels)
{
Init();

var options = new Options()
{
SqlTransactionIsolationLevel = transactionLevel,
CommandTimeoutSeconds = 60,
FireTriggers = false,
KeepIdentity = false,
NotifyAfter = 4,
ConvertEmptyPropertyValuesToNull = false,
KeepNulls = true,
TableLock = false,
};

var result = await MicrosoftSQL.BulkInsert(_input, options, default);
Assert.IsTrue(result.Success);
Assert.AreEqual(0, result.Count);
Assert.AreEqual(3, GetRowCount());

await MicrosoftSQL.BulkInsert(_input, options, default);
Assert.AreEqual(6, GetRowCount());

CleanUp();
}
}

[TestMethod]
public async Task TestBulkInsert_NotifyAfterOne()
{
var transactionLevels = new List<SqlTransactionIsolationLevel>() {
SqlTransactionIsolationLevel.Unspecified,
SqlTransactionIsolationLevel.Serializable,
SqlTransactionIsolationLevel.None,
SqlTransactionIsolationLevel.ReadUncommitted,
SqlTransactionIsolationLevel.ReadCommitted
};

foreach (var transactionLevel in transactionLevels)
{
Init();

var options = new Options()
{
SqlTransactionIsolationLevel = transactionLevel,
CommandTimeoutSeconds = 60,
FireTriggers = false,
KeepIdentity = false,
NotifyAfter = 1,
ConvertEmptyPropertyValuesToNull = false,
KeepNulls = true,
TableLock = false,
};

var result = await MicrosoftSQL.BulkInsert(_input, options, default);
Assert.IsTrue(result.Success);
Assert.AreEqual(3, result.Count);
Assert.AreEqual(3, GetRowCount());

await MicrosoftSQL.BulkInsert(_input, options, default);
Assert.AreEqual(6, GetRowCount());

CleanUp();
}
}

private static int GetRowCount()
{
using var connection = new SqlConnection(_connString);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Frends.MicrosoftSQL.BulkInsert.Definitions;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
using System;
using System.ComponentModel;
Expand All @@ -8,7 +9,6 @@
using System.Runtime.Loader;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;

namespace Frends.MicrosoftSQL.BulkInsert;

Expand Down Expand Up @@ -121,18 +121,28 @@ private static async Task<long> ExecuteHandler(Options options, string tableName
sqlBulkCopy.BulkCopyTimeout = options.CommandTimeoutSeconds;
sqlBulkCopy.DestinationTableName = tableName;
sqlBulkCopy.SqlRowsCopied += (s, e) => rowsCopied = e.RowsCopied;
sqlBulkCopy.NotifyAfter = options.NotifyAfter;

await sqlBulkCopy.WriteToServerAsync(dataSet.Tables[0], cancellationToken).ConfigureAwait(false);
if (options.NotifyAfter == 0)
{
// Calculate the number of rows and set value for NotifyAfter
var rowCount = dataSet.Tables[0].Rows.Count;
sqlBulkCopy.NotifyAfter = rowCount > 0 ? Math.Max(1, rowCount / 10) : 1;
}
else if (options.NotifyAfter > 0)
sqlBulkCopy.NotifyAfter = options.NotifyAfter;
else
sqlBulkCopy.NotifyAfter = 0;

return rowsCopied;
await sqlBulkCopy.WriteToServerAsync(dataSet.Tables[0], cancellationToken).ConfigureAwait(false);
}
}
catch (Exception ex)
{
var notifyRange = rowsCopied + (options.NotifyAfter - 1);
throw new Exception($"ExecuteHandler exception, procecced row count between: {rowsCopied} and {notifyRange} (see Options.NotifyAfter). {ex}");
var notifyRange = rowsCopied + (sqlBulkCopy.NotifyAfter - 1);
throw new Exception($"ExecuteHandler exception, processed row count between: {rowsCopied} and {notifyRange} (see NotifyAfter). {ex}");
}

return rowsCopied;
}

private static void SetEmptyDataRowsToNull(DataSet dataSet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ public class Options
public int CommandTimeoutSeconds { get; set; }

/// <summary>
/// Defines the number of rows to be processed before generating a notification event. Range: 0 - 'count of rows to be processed'
/// Notification event can be used for error handling to see approximately which row the error happened.
/// Default value 0 = There won't be any notifications until the task is completed.
/// 10 = The counter is updated after every 10 rows or when every row has been processed.
/// Defines the number of rows to be processed before generating a notification event.
/// The default value of 0 will set NotifyAfter dynamically to 10% of the total row count, with a minimum value of 1.
/// A value of -1 means there won't be any notifications until the task is completed, and Result.Count will be 0.
/// Setting a value greater than the total number of rows can cause Result.Count to be 0.
/// Notification events can be used for error handling to see approximately which row the error occurred at.
/// </summary>
/// <example>0</example>
public int NotifyAfter { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<Version>2.1.0</Version>
<Version>2.2.0</Version>
<Authors>Frends</Authors>
<Copyright>Frends</Copyright>
<Company>Frends</Company>
Expand Down

0 comments on commit 49e7c01

Please sign in to comment.