ADO.NET · Advanced

SqlBulkCopy

Inserting 100,000 rows one by one takes minutes. SqlBulkCopy does it in seconds using SQL Server’s bulk load protocol.

Advanced 5 min Lesson 23 of 32Examples use Microsoft.Data.SqlClient on .NET 9 with SQL Server 2022.
Before you start:DataSet and DataTable
0/32 in ADO.NET
On this page · 5 min · Advanced

In simple words#

Instead of carrying 10,000 bricks one by one, you bring a truck. SqlBulkCopy loads thousands of rows in one fast operation.

SqlBulkCopy
Fast bulk insert.
ColumnMappings
Match source and destination columns by name.
BatchSize
Rows sent per batch.

Learning objectives#

  • Bulk insert from a DataTable
  • Map columns explicitly
  • Tune BatchSize and options
  • Use a staging table for merges

Explanation#

SqlBulkCopy streams rows from a DataTable, DataRow[] or any IDataReader into a table.

Always add ColumnMappings: without them, columns are matched by position, which breaks when the table has an identity column or a different order.

BatchSize controls how many rows go per batch; SqlBulkCopyOptions.TableLock speeds up large loads; CheckConstraints and FireTriggers are off by default.

For “insert or update”, bulk load into a staging table, then run one MERGE or UPDATE/INSERT statement.

Code example: Import a CSV price list#

C#BulkImport.cs Illustrative
var table = new DataTable();
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Price", typeof(decimal));
table.Columns.Add("Stock", typeof(int));


foreach (var line in File.ReadLines("products.csv").Skip(1))   // Name,Price,Stock
{
    var parts = line.Split(',');
    table.Rows.Add(parts[0], decimal.Parse(parts[1], CultureInfo.InvariantCulture), int.Parse(parts[2]));
}


await using var conn = new SqlConnection(cs);
await conn.OpenAsync();
using var bulk = new SqlBulkCopy(conn, SqlBulkCopyOptions.TableLock | SqlBulkCopyOptions.CheckConstraints, null)
{
    DestinationTableName = "dbo.Products",
    BatchSize = 5000,
    NotifyAfter = 20000,
};
bulk.ColumnMappings.Add("Name", "Name");
bulk.ColumnMappings.Add("Price", "Price");
bulk.ColumnMappings.Add("Stock", "Stock");
bulk.SqlRowsCopied += (_, e) => Console.WriteLine(
quot;
{e.RowsCopied:N0} rows…");
var sw = Stopwatch.StartNew(); await bulk.WriteToServerAsync(table); Console.WriteLine(
quot;Imported
{table.Rows.Count:N0} rows in {sw.Elapsed.TotalSeconds:F1}s");
Expected output
20,000 rows…
40,000 rows…
...
Imported 100,000 rows in 1.8s   (timings vary)

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. The CSV is parsed into a DataTable with the right types.
  2. TableLock and CheckConstraints are enabled.
  3. Explicit mappings skip the identity Id column.
  4. SqlRowsCopied reports progress every 20,000 rows.

Real-world use case#

A nightly job imports a supplier’s 200,000-row price file into a staging table, then MERGEs it into Products in one statement.

Common mistakes#

No column mappings

Position-based mapping silently puts values in the wrong columns or fails with type errors.

Expecting constraints and triggers to run

They are skipped by default. Enable CheckConstraints/FireTriggers if you rely on them.

Best practices#

  • Always map columns
  • Load into a staging table for upserts
  • Use an IDataReader source for huge files to avoid memory spikes
  • Wrap in a transaction if the load must be all-or-nothing

Remember this#

Many rows → SqlBulkCopy, not a loop of INSERTs.

Map columns explicitly.

Upsert = bulk to staging + MERGE.

Practice exercise#

Your task: Bulk copy into dbo.ProductsStaging instead and then insert only new names into Products.

Show solution
SQL Illustrative
INSERT INTO dbo.Products (Name, Price, Stock)
SELECT s.Name, s.Price, s.Stock
FROM dbo.ProductsStaging s
WHERE NOT EXISTS (SELECT 1 FROM dbo.Products p WHERE p.Name = s.Name);
TRUNCATE TABLE dbo.ProductsStaging;

Mini quiz#

SqlBulkCopy quizMultiple choice Question 1 of 1

Why add ColumnMappings?

Score so far: 0/0

Want more? Practise all ADO.NET MCQs · ADO.NET interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.