如何使用SqlBulkCopy从简单对象的List<>进行大批量插入?
我需要实现自定义的IDataReader吗?
如何使用SqlBulkCopy从简单对象的List<>进行大批量插入?
我需要实现自定义的IDataReader吗?
使用FastMember,您可以在不经过DataTable
的情况下完成此操作(在我的测试中,这种方式的性能提高了两倍以上):
using(var bcp = new SqlBulkCopy(connection))
using(var reader = ObjectReader.Create(data, "Id", "Name", "Description"))
{
bcp.DestinationTableName = "SomeTable";
bcp.WriteToServer(reader);
}
请注意,ObjectReader
还可以与非泛型源一起使用,并且不需要预先指定成员名称(尽管如果您没有在ObjectReader
本身中指定它们,则可能希望使用SqlBulkCopy
的ColumnMappings
方面)。
DataTable
太慢了,所以我想使用这种方法。然而,在params变量中列出的字符串是否是正在使用的变量的实际名称,以便从底层数据结构迭代的对象中按顺序使用它们? - JNYRanger只需从对象列表中创建一个DataTable,然后调用SqlBulkCopy.WriteToServer
,将数据表传递进去即可。
您可能会发现以下内容有用:
为了获得最佳的SqlBulkCopy性能,您应该设置适当的BatchSize。 10,000似乎效果很好-但要针对您的数据进行分析。
当使用SqlBulkCopyOptions.TableLock时,您可能还会观察到更好的结果。
关于SqlBulkCopy性能的有趣且有启发性的分析可以在这里找到。
var connStr = "";
using (var connection = new SqlConnection(connStr))
{
var startTime = DateTime.Now;
connection.Open();
var transaction = connection.BeginTransaction();
try
{
//var connStr = connection.ConnectionString;
using (var sbCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction))
{
sbCopy.BulkCopyTimeout = 0;
sbCopy.BatchSize = 10000;
sbCopy.DestinationTableName = "Foobars";
var reader = Foobars.AsDataReader();
sbCopy.WriteToServer(reader);
}
transaction.Commit();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
transaction.Rollback();
}
finally
{
transaction.Dispose();
connection.Close();
var endTime = DateTime.Now;
Console.WriteLine("Upload time elapsed: {0} seconds", (endTime - startTime).TotalSeconds);
}
}
sbCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping("col1", "col1"));
- Jaime BennettSqlBulkCopy
的目的,使用表值参数(TVP)可能更有意义。使用TVP可以轻松地发送任何自定义类型的集合。数据可以流式传输,因此可以避免DataTable
(就像@Marc Gravell的答案中一样),也可以避免SqlBulkCopy
。TVP允许完全灵活地处理数据,因为您调用存储过程将TVP数据传递到SQL Server,并且它显示为一个表变量,您可以对其进行任何操作,而不仅仅是INSERT
(这是SqlBulkCopy
的情况)。您还可以通过SqlDataReader
获取数据,例如新创建的IDENTITY
值。我在这个答案中添加了一个示例和一些附加说明: 如何在最短时间内插入1000万条记录?。几年前,我在SQL Server Central上写了一篇文章(需要免费注册),从应用程序流式传输数据到SQL Server 2008,该文章也在那个链接的答案中提到,提供了一个传递自定义类型的通用列表的工作示例,从一个300万行文本文件中流式传输。在尝试将数百万行插入数据库时,遇到了类似的情况。
通过将列表转换为 DataTable,然后将表格插入数据库来完成此操作。
private static DataTable CreateDataTableItem(List<ListItem> ItemList)
{
DataTable dt = new DataTable();
try
{
dt.TableName = "PartPrice";
foreach (PropertyInfo property in typeof(ListItem).GetProperties())
{
dt.Columns.Add(new DataColumn() { ColumnName = property.Name, DataType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType, AllowDBNull = true });
}
foreach (var item in ItemList)
{
DataRow newRow = dt.NewRow();
foreach (PropertyInfo property in typeof(ListItem).GetProperties())
{
newRow[property.Name] = item.GetType().GetProperty(property.Name)?.GetValue(item, null) ?? DBNull.Value;
}
dt.Rows.Add(newRow);
}
return dt;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return null;
}
}
public class ListItem
{
public int Id { get; set; }
public string Name { get; set; }
public int? NullableId { get; set; }
}
然后使用批量插入
private void BulkInsert(DataTable dt)
{
string consString = _config.GetConnectionString("yourConnectionStringkey");
using SqlConnection connection = new SqlConnection(consString);
connection.Open();
using var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock | SqlBulkCopyOptions.UseInternalTransaction, null);
sqlBulkCopy.DestinationTableName = "dbo.TargetDb";
sqlBulkCopy.ColumnMappings.Add("Id", "Id");
sqlBulkCopy.ColumnMappings.Add("Name", "Name");
sqlBulkCopy.ColumnMappings.Add("NullableId", "NullableId");
sqlBulkCopy.WriteToServer(dt);
connection.Close();
}
您不必执行给定的列映射
// Summary:
// Returns a collection of Microsoft.Data.SqlClient.SqlBulkCopyColumnMapping items.
// Column mappings define the relationships between columns in the data source and
// columns in the destination.
//
// Value:
// A collection of column mappings. By default, it is an empty collection.
//
// Remarks:
// If the data source and the destination table have the same number of columns,
// and the ordinal position of each source column within the data source matches
// the ordinal position of the corresponding destination column, the <xref:Microsoft.Data.SqlClient.SqlBulkCopy.ColumnMappings>
// collection is unnecessary. However, if the column counts differ, or the ordinal
// positions are not consistent, you must use <xref:Microsoft.Data.SqlClient.SqlBulkCopy.ColumnMappings>
// to make sure that data is copied into the correct columns. During the execution
// of a bulk copy operation, this collection can be accessed, but it cannot be changed.
// Any attempt to change it will throw an <xref:System.InvalidOperationException>.
这两行代码允许您将可空值插入数据表列中
{ ColumnName = property.Name, DataType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType, AllowDBNull = true }
newRow[property.Name] = item.GetType().GetProperty(property.Name)?.GetValue(item, null) ?? DBNull.Value;
EntityDataReader
类,就会有一个AsDataReader()
扩展方法可以完全实现这一点:https://github.com/matthewschrager/Repository/blob/master/Repository.EntityFramework/EntityDataReader.cs - RJB