diff --git a/Frends.MicrosoftSQL.BulkInsert/CHANGELOG.md b/Frends.MicrosoftSQL.BulkInsert/CHANGELOG.md
index 2457cbd..0d1fbab 100644
--- a/Frends.MicrosoftSQL.BulkInsert/CHANGELOG.md
+++ b/Frends.MicrosoftSQL.BulkInsert/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## [3.3.0] - 2026-06-18
+
+### Changed
+
+- In successful execution, Result.Count will show number of all rows.
+- In case of failure, Result.Count will show estimated number of rows copied before the failure.
+
## [3.2.0] - 2026-06-18
### Changed
diff --git a/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert.Tests/UnitTests.cs b/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert.Tests/UnitTests.cs
index 1f5de20..3cf3f45 100644
--- a/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert.Tests/UnitTests.cs
+++ b/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert.Tests/UnitTests.cs
@@ -332,7 +332,7 @@ public async Task TestBulkInsert_NotifyAfterZero()
var result = await MicrosoftSQL.BulkInsert(_input, options, default);
Assert.IsTrue(result.Success);
- Assert.AreEqual(0, result.Count);
+ Assert.AreEqual(3, result.Count);
Assert.AreEqual(3, GetRowCount());
await MicrosoftSQL.BulkInsert(_input, options, default);
@@ -371,7 +371,7 @@ public async Task TestBulkInsert_NotifyAfterTooMuch()
var result = await MicrosoftSQL.BulkInsert(_input, options, default);
Assert.IsTrue(result.Success);
- Assert.AreEqual(0, result.Count);
+ Assert.AreEqual(3, result.Count);
Assert.AreEqual(3, GetRowCount());
await MicrosoftSQL.BulkInsert(_input, options, default);
@@ -489,4 +489,4 @@ private static int GetRowCount()
connection.Dispose();
return count;
}
-}
\ No newline at end of file
+}
diff --git a/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/BulkInsert.cs b/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/BulkInsert.cs
index 5429bfa..c98c972 100644
--- a/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/BulkInsert.cs
+++ b/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/BulkInsert.cs
@@ -24,17 +24,20 @@ public class MicrosoftSQL
/// Optional parameters
/// Token generated by Frends to stop this Task.
/// Object { bool Success, long Count, string ErrorMessage }
- public static async Task BulkInsert([PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken)
+ public static async Task BulkInsert([PropertyTab] Input input, [PropertyTab] Options options,
+ CancellationToken cancellationToken)
{
var inputJson = @"{""data"": {""Table"": " + input.InputData + @"
}
}";
+ long rowsCopied;
try
{
DataSet dataSet = JObject.Parse(inputJson)["data"].ToObject();
_ = dataSet.Tables["Table"];
+
using var connection = new SqlConnection(input.ConnectionString);
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
@@ -47,15 +50,23 @@ public static async Task BulkInsert([PropertyTab] Input input, [Property
{
try
{
- var result = await ExecuteHandler(options, input, dataSet, new SqlBulkCopy(connection, GetSqlBulkCopyOptions(options), null), cancellationToken).ConfigureAwait(false);
+ var result = await ExecuteHandler(options, input, dataSet,
+ new SqlBulkCopy(connection, GetSqlBulkCopyOptions(options), null), cancellationToken)
+ .ConfigureAwait(false);
+
return new Result(true, result, null);
}
catch (Exception ex)
{
if (options.ThrowErrorOnFailure)
- throw new Exception("BulkInsert exception: 'Options.SqlTransactionIsolationLevel = None', so there was no transaction rollback.", ex);
- else
- return new Result(false, 0, $"ExecuteHandler exception: 'Options.SqlTransactionIsolationLevel = None', so there was no transaction rollback. {ex}");
+ throw new Exception(
+ "BulkInsert exception: 'Options.SqlTransactionIsolationLevel = None', so there was no transaction rollback.",
+ ex);
+
+ rowsCopied = ex.Data["RowsCopied"] != null ? (long)ex.Data["RowsCopied"] : 0;
+
+ return new Result(false, rowsCopied,
+ $"ExecuteHandler exception: 'Options.SqlTransactionIsolationLevel = None', so there was no transaction rollback. {ex}");
}
}
@@ -64,8 +75,11 @@ public static async Task BulkInsert([PropertyTab] Input input, [Property
try
{
- var result = await ExecuteHandler(options, input, dataSet, new SqlBulkCopy(connection, GetSqlBulkCopyOptions(options), transaction), cancellationToken).ConfigureAwait(false);
+ var result = await ExecuteHandler(options, input, dataSet,
+ new SqlBulkCopy(connection, GetSqlBulkCopyOptions(options), transaction), cancellationToken)
+ .ConfigureAwait(false);
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
+
return new Result(true, result, null);
}
catch (Exception ex)
@@ -77,15 +91,23 @@ public static async Task BulkInsert([PropertyTab] Input input, [Property
catch (Exception rollbackEx)
{
if (options.ThrowErrorOnFailure)
- throw new Exception("BulkInsert exception: An exception occurred on transaction rollback.", rollbackEx);
- else
- return new Result(false, 0, $"BulkInsert exception: An exception occurred on transaction rollback. Rollback exception: {rollbackEx}. || Exception leading to rollback: {ex}");
+ throw new Exception("BulkInsert exception: An exception occurred on transaction rollback.",
+ rollbackEx);
+
+ rowsCopied = ex.Data["RowsCopied"] != null ? (long)ex.Data["RowsCopied"] : 0;
+
+ return new Result(false, rowsCopied,
+ $"BulkInsert exception: An exception occurred on transaction rollback. Rollback exception: {rollbackEx}. || Exception leading to rollback: {ex}");
}
if (options.ThrowErrorOnFailure)
- throw new Exception("BulkInsert exception: (If required) transaction rollback completed without exception.", ex);
- else
- return new Result(false, 0, $"BulkInsert exception: (If required) transaction rollback completed without exception. {ex}.");
+ throw new Exception(
+ "BulkInsert exception: (If required) transaction rollback completed without exception.", ex);
+
+ rowsCopied = ex.Data["RowsCopied"] != null ? (long)ex.Data["RowsCopied"] : 0;
+
+ return new Result(false, rowsCopied,
+ $"BulkInsert exception: (If required) transaction rollback completed without exception. {ex}.");
}
}
catch (Exception e)
@@ -93,7 +115,11 @@ public static async Task BulkInsert([PropertyTab] Input input, [Property
if (options.ThrowErrorOnFailure)
throw new Exception("BulkInsert exception: ", e);
else
- return new Result(false, 0, $"BulkInsert exception: {e}");
+ {
+ rowsCopied = e.Data["RowsCopied"] != null ? (long)e.Data["RowsCopied"] : 0;
+
+ return new Result(false, rowsCopied, $"BulkInsert exception: {e}");
+ }
}
finally
{
@@ -101,9 +127,11 @@ public static async Task BulkInsert([PropertyTab] Input input, [Property
}
}
- private static async Task ExecuteHandler(Options options, Input input, DataSet dataSet, SqlBulkCopy sqlBulkCopy, CancellationToken cancellationToken)
+ private static async Task ExecuteHandler(Options options, Input input, DataSet dataSet,
+ SqlBulkCopy sqlBulkCopy, CancellationToken cancellationToken)
{
var rowsCopied = 0L;
+ var totalRows = dataSet.Tables[0].Rows.Count;
// JsonPropertyOrder is handled implicitly (default behavior) by not adding any column mappings,
// which means the columns will be mapped based on their order in the input JSON.
@@ -126,39 +154,49 @@ private static async Task ExecuteHandler(Options options, Input input, Dat
sqlBulkCopy.DestinationTableName = input.TableName;
sqlBulkCopy.SqlRowsCopied += (s, e) => rowsCopied = e.RowsCopied;
- if (options.NotifyAfter == 0)
+ sqlBulkCopy.NotifyAfter = options.NotifyAfter switch
{
- // 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;
+ 0 => totalRows > 0 ? Math.Max(1, totalRows / 10) : 1,
+ > 0 => options.NotifyAfter,
+ _ => 0,
+ };
await sqlBulkCopy.WriteToServerAsync(dataSet.Tables[0], cancellationToken).ConfigureAwait(false);
}
}
catch (Exception ex)
{
- var notifyRange = rowsCopied + (sqlBulkCopy.NotifyAfter - 1);
- throw new Exception($"ExecuteHandler exception, processed row count between: {rowsCopied} and {notifyRange} (see NotifyAfter). {ex}");
+ var notifyAfter = sqlBulkCopy.NotifyAfter;
+ var notifyRange = notifyAfter > 0 ? rowsCopied + notifyAfter - 1 : rowsCopied;
+
+ throw new Exception(
+ $"ExecuteHandler exception, processed row count between: {rowsCopied} and {notifyRange} (see NotifyAfter). {ex}",
+ ex)
+ {
+ Data =
+ {
+ ["RowsCopied"] = rowsCopied,
+ },
+ };
}
- return rowsCopied;
+ //If code goes up to here, it means all rows were inserted.
+ return totalRows;
}
private static void SetEmptyDataRowsToNull(DataSet dataSet)
{
foreach (var table in dataSet.Tables.Cast())
+ {
foreach (var row in table.Rows.Cast())
- foreach (var column in row.ItemArray)
- if (column.ToString() == string.Empty)
- {
- var index = Array.IndexOf(row.ItemArray, column);
- row[index] = null;
- }
+ {
+ for (var i = 0; i < row.ItemArray.Length; i++)
+ {
+ if (row[i] is string value && value.Length == 0)
+ row[i] = DBNull.Value;
+ }
+ }
+ }
}
private static SqlBulkCopyOptions GetSqlBulkCopyOptions(Options options)
@@ -193,4 +231,4 @@ private static IsolationLevel GetIsolationLevel(Options options)
_ => IsolationLevel.ReadCommitted,
};
}
-}
\ No newline at end of file
+}
diff --git a/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Definitions/Options.cs b/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Definitions/Options.cs
index b120d23..773a6ba 100644
--- a/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Definitions/Options.cs
+++ b/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Definitions/Options.cs
@@ -15,10 +15,10 @@ public class Options
public int CommandTimeoutSeconds { get; set; }
///
- /// Defines the number of rows to be processed before generating a notification event.
+ /// 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.
+ /// A value of -1 means there won't be any notifications until the task is completed.
+ /// Setting a value greater than the total number of rows can cause notification response to be 0.
/// Notification events can be used for error handling to see approximately which row the error occurred at.
///
/// 0
@@ -46,7 +46,7 @@ public class Options
public bool TableLock { get; set; }
///
- /// Preserve null values in the destination table regardless of the settings for default values.
+ /// Preserve null values in the destination table regardless of the settings for default values.
/// When not specified, null values are replaced by default values where applicable.
///
/// false
@@ -82,4 +82,4 @@ public class Options
/// SqlTransactionIsolationLevel.ReadCommitted
[DefaultValue(SqlTransactionIsolationLevel.ReadCommitted)]
public SqlTransactionIsolationLevel SqlTransactionIsolationLevel { get; set; }
-}
\ No newline at end of file
+}
diff --git a/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Definitions/Result.cs b/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Definitions/Result.cs
index 04818be..d9c500f 100644
--- a/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Definitions/Result.cs
+++ b/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Definitions/Result.cs
@@ -12,8 +12,8 @@ public class Result
public bool Success { get; private set; }
///
- /// Number of processed rows reported by SqlBulkCopy notifications.
- /// The value is approximate and can be rounded down to the nearest NotifyAfter interval (or 0 if no notification is raised).
+ /// Number of processed rows.
+ /// In case of failure it shows notified number of processed rows. Approximation logic is defined by Options.NotifyAfter
///
/// 100
public long Count { get; private set; }
diff --git a/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert.csproj b/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert.csproj
index 09247b8..db33c1a 100644
--- a/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert.csproj
+++ b/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert/Frends.MicrosoftSQL.BulkInsert.csproj
@@ -2,7 +2,7 @@
net6.0
- 3.2.0
+ 3.3.0
Frends
Frends
Frends