Bulk Inserts
This scenario demonstrate how to perform bulk inserts. In order to improve performance, this uses database-specific APIs rather than SQL.
is used for large collections where SQL isn't appropriate. operations on collections of 1,000 to 100,000 objects. Some ORMs require special handling for collections of this size. Others use the same pattern seen in .
Smaller collections should be handled with a batch insert. This is described in CRUD Operations on Multiple Objects and Batch Inserts with Large Collections
Scenario Prototype
public interface IBulkInsertScenario<TEmployeeSimple>
where TEmployeeSimple : class, IEmployeeSimple, new()
{
/// <summary>
/// Gets a collection of Employee rows by their name. Assume the name is not unique.
/// </summary>
int CountByLastName(string lastName);
/// <summary>
/// Insert a large collection of Employee rows.
/// </summary>
void BulkInsert(IList<TEmployeeSimple> employees);
/// <summary>
/// Insert a large collection of Employee rows using a DataTable.
/// </summary>
/// <param name="employees"></param>
void BulkInsert(DataTable employees);
}
For ORMs that do not support bulk inserts from objects, this mapping function is provided.
[SuppressMessage("Design", "CA1000")]
public static DataTable CopyToDataTable(IEnumerable<IEmployeeSimple> employees)
{
if (employees == null || !employees.Any())
throw new ArgumentException($"{nameof(employees)} is null or empty.", nameof(employees));
var result = new DataTable();
result.Columns.Add("CellPhone", typeof(string));
result.Columns.Add("EmployeeClassificationKey", typeof(int));
result.Columns.Add("FirstName", typeof(string));
result.Columns.Add("MiddleName", typeof(string));
result.Columns.Add("LastName", typeof(string));
result.Columns.Add("OfficePhone", typeof(string));
result.Columns.Add("Title", typeof(string));
foreach (var employee in employees)
{
var row = result.NewRow();
row["CellPhone"] = employee.CellPhone;
row["EmployeeClassificationKey"] = employee.EmployeeClassificationKey;
row["FirstName"] = employee.FirstName;
row["MiddleName"] = employee.MiddleName;
row["LastName"] = employee.LastName;
row["OfficePhone"] = employee.OfficePhone;
row["Title"] = employee.Title;
result.Rows.Add(row);
}
return result;
}
ADO.NET
ADO only supports bulk inserts from a DataTable.
public void BulkInsert(IList<EmployeeSimple> employees)
{
if (employees == null || employees.Count == 0)
throw new ArgumentException($"{nameof(employees)} is null or empty.", nameof(employees));
var dataaTable = Utilities.CopyToDataTable(employees);
using (var con = OpenConnection())
using (var sbc = new SqlBulkCopy(con))
{
sbc.DestinationTableName = "HR.Employee";
foreach (DataColumn? column in dataaTable.Columns)
sbc.ColumnMappings.Add(column!.ColumnName, column.ColumnName);
sbc.WriteToServer(dataaTable);
}
}
public void BulkInsert(DataTable employees)
{
if (employees == null)
throw new ArgumentNullException(nameof(employees), $"{nameof(employees)} is null.");
using (var con = OpenConnection())
using (var sbc = new SqlBulkCopy(con))
{
sbc.DestinationTableName = "HR.Employee";
foreach (DataColumn? column in employees.Columns)
sbc.ColumnMappings.Add(column!.ColumnName, column.ColumnName);
sbc.WriteToServer(employees);
}
}
Chain
public void BulkInsert(IList<EmployeeSimple> employees)
{
m_DataSource.InsertBulk(employees).Execute();
}
public void BulkInsert(DataTable employees)
{
m_DataSource.InsertBulk<EmployeeSimple>(employees).Execute();
}
Dapper
Dapper does not have support for bulk inserts.
DbConnector
Bulk inserts are an ADO.NET "data provider specific" implementation therefore DbConnector is not involved or necessary.
public void BulkInsert(IList<EmployeeSimple> employees)
{
if (employees == null || employees.Count == 0)
throw new ArgumentException($"{nameof(employees)} is null or empty.", nameof(employees));
DataTable dtEmployees = Utilities.CopyToDataTable(employees);
//This implementation is ADO.NET "data provider specific" therefore DbConnector is not involved
using (var con = OpenConnection())
using (var sbc = new SqlBulkCopy(con))
{
sbc.DestinationTableName = "HR.Employee";
foreach (DataColumn? column in dtEmployees.Columns)
sbc.ColumnMappings.Add(column!.ColumnName, column.ColumnName);
sbc.WriteToServer(dtEmployees);
}
}
public void BulkInsert(DataTable employees)
{
if (employees == null)
throw new ArgumentNullException(nameof(employees), $"{nameof(employees)} is null.");
//This implementation is ADO.NET "data provider specific" therefore DbConnector is not involved
using (var con = OpenConnection())
using (var sbc = new SqlBulkCopy(con))
{
sbc.DestinationTableName = "HR.Employee";
foreach (DataColumn? column in employees.Columns)
sbc.ColumnMappings.Add(column!.ColumnName, column.ColumnName);
sbc.WriteToServer(employees);
}
}
Entity Framework 6
Entity Framework does not have support for bulk inserts.
Entity Framework Core
Entity Framework Core does not have support for bulk inserts.
LINQ to DB
LinqToDB only supports bulk inserts from a collection of objects.
Note the use of BulkCopyType.ProviderSpecific
.
public void BulkInsert(IList<Employee> employees)
{
var options = new BulkCopyOptions() { BulkCopyType = BulkCopyType.ProviderSpecific };
using (var db = new OrmCookbook())
db.BulkCopy(options, employees);
}
public void BulkInsert(DataTable employees)
{
Assert.Inconclusive("Bulk insert using a DataTable is not supported.");
}
For more information see Bulk Copy (Bulk Insert)
LLBLGen Pro
LLBLGen Pro does not have support for bulk inserts.
NHibernate
NHibernate does not have support for bulk inserts.
RepoDb
public void BulkInsert(IList<EmployeeSimple> employees)
{
if (employees == null || employees.Count == 0)
throw new ArgumentException($"{nameof(employees)} is null or empty.", nameof(employees));
this.BulkInsert<EmployeeSimple>(employees);
}
public void BulkInsert(DataTable employees)
{
if (employees == null)
throw new ArgumentNullException(nameof(employees), $"{nameof(employees)} is null.");
this.BulkInsert<EmployeeSimple>(employees, rowState: DataRowState.Added);
}
ServiceStack
ServiceStack does not have support for bulk inserts.