Error Handling
BulkSharp tracks errors at two levels: operation-level (metadata validation, scheduling) and row-level (validation, processing, step execution).
Row-Level Errors
Every row is processed independently. A single row failure does not stop the operation -- it is recorded as a BulkRowRecord with an error state and processing continues with the next row.
Errors are classified via the BulkErrorType enum:
| Value | Meaning |
|---|---|
Validation |
Row failed ValidateRowAsync or a composed IBulkRowValidator |
Processing |
Row failed during ProcessRowAsync or simple row execution |
StepFailure |
A pipeline step exceeded its MaxRetries |
Timeout |
An async step timed out waiting for external completion |
SignalFailure |
An external signal reported a failure |
Querying Errors
Errors are stored as BulkRowRecord entries with ErrorType set. Query them via IBulkRowRecordRepository:
var errors = await rowRecordRepo.QueryAsync(new BulkRowRecordQuery
{
OperationId = operationId,
ErrorsOnly = true, // only records with errors
ErrorType = BulkErrorType.Validation, // optional: filter by type
RowNumber = 42, // optional: filter by row number
RowId = "ORD-123", // optional: filter by business key
Page = 1,
PageSize = 50,
SortBy = "RowNumber",
SortDescending = false
});
Console.WriteLine($"Total errors: {errors.TotalCount}");
Console.WriteLine($"Has more pages: {errors.HasNextPage}");
foreach (var record in errors.Items)
{
Console.WriteLine($"Row {record.RowNumber} [{record.ErrorType}]: {record.ErrorMessage}");
}
Including Row Data in Errors
By default, error records do not include the row data that caused the error. To include it, set TrackRowData = true on the operation attribute:
[BulkOperation("import-users", TrackRowData = true)]
public class UserImportOperation : IBulkRowOperation<UserMetadata, UserRow> { ... }
When enabled, record.RowData contains the JSON-serialized row stored during the validation phase. Warning: this may contain PII. Disable in production if rows contain sensitive data.
You can also enable row data globally:
services.AddBulkSharp(builder => builder
.ConfigureOptions(opts => opts.IncludeRowDataInErrors = true));
Operation-Level Errors
When metadata validation fails or an unrecoverable error occurs, the entire operation is marked as Failed:
var operation = await service.GetBulkOperationAsync(operationId);
if (operation!.Status == BulkOperationStatus.Failed)
{
Console.WriteLine($"Operation failed: {operation.ErrorMessage}");
}
Operation Status Transitions
| Status | Meaning |
|---|---|
Pending |
Created, waiting to be processed |
Running |
Currently processing rows |
Completed |
All rows processed successfully |
CompletedWithErrors |
Processing finished but some rows failed |
Failed |
Operation-level failure (metadata validation, file error, etc.) |
Cancelled |
Cancelled by user |
Error Recovery via Retry
For operations marked as retryable, failed rows can be retried after the operation completes with errors. The retry feature:
- Snapshots each failed row's error state into
BulkRowRetryHistorybefore resetting - Resets failed rows to
Pendingand re-queues the operation - Resumes pipeline operations from the failing step (skips completed steps)
- Recalculates counters from actual row record states after retry
Retry excludes validation failures (StepIndex = -1) -- only rows that failed during processing steps can be retried.
See Retry Guide for configuration, API usage, and a full walkthrough.
Batch Error Writing
Row records (including errors) are written in batches for performance. The FlushBatchSize option (default: 100) controls how many rows are processed between flushes. This means errors may not appear in queries immediately during processing -- they are flushed periodically and at operation completion.