Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Options.NotifyAfter fix. #56

Merged
merged 5 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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; }
ttossavainen marked this conversation as resolved.
Show resolved Hide resolved
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
Loading