使用 Sql 插入语句和 SqlBulkCopy 插入数据有什么区别?
What is the difference between inserting data using Sql insert statements and SqlBulkCopy?
我在向 SQL 服务器插入大量数据时遇到问题。
之前我使用的是 Entity framework,但是对于仅 100K 根级记录(分别包含两个不同的集合,其中每个集合进一步操作大约 200K 条记录)来说速度太慢了 = 大约 500K-600K 条记录在记忆中。
在这里我应用了所有优化(例如 AutoDetectChangesEnabled = false,并在每批之后重新创建和处理上下文。)
我拒绝了该解决方案,并使用了 BulkInsert,它非常快速且高效。只能在一分钟左右插入 100K 条记录。
但主要问题是从新插入的记录中取回主键。为此,我正在考虑编写可以在 TVP 上运行的存储过程(即内存数据table 持有所有根级 100K 记录)。
在里面我会使用 OUTPUT INSERTED.Id 来获取应用程序中的所有主键)。
那么,我如何比较这种方法(即 Sql 在存储过程中插入查询)与 SqlBulkCopy 方法。
知道我是否可以在 SqlBulkCopy 操作后取回所有主键吗?或者关于 OUTPUT Inserted.Id 的具体内容 return 应用程序中所有正确的新密钥。
PS : 我不想在此过程中创建任何暂存 table。这只是开销。
这是一个基于评论中的讨论/扩展此处提到的想法的示例:Possible to get PrimayKey IDs back after a SQL BulkCopy?
即
- 从 C# 批量上传到 SQL
中的临时文件 table
- 使用Sql将数据从临时table复制到实际table(此时生成ID),return ID。
我还没有机会对此进行测试,但希望这会有所帮助:
//using System.Data.SqlClient;
//using System.Collections.Generic;
public DataTable CreatePersonDataTable(IEnumerable<PersonDTO> people)
{
//define the table
var table = new DataTable("People");
table.Columns.Add(new DataColumn("Name", typeof(string)));
table.Columns.Add(new DataColumn("DOB", typeof(DateTime)));
//populate it
foreach (var person in people)
{
table.Rows.Add(person.Name, person.DOB);
}
return table;
}
readonly string ConnectionString; //set this in the constructor
readonly int BulkUploadPeopleTimeoutSeconds = 600; //default; could override in constructor
public IEnumerable<long> BulkUploadPeople(IEnumerable<PersonDTO> people) //you'd want to break this up a bit; for simplicty I've bunged everything into one big method
{
var data = CreatePersonDataTable(people);
using(SqlConnection con = new SqlConnection(ConnectionString))
{
con.Open(); //keep same connection open throughout session
RunSqlNonQuery(con, "select top 0 Name, DOB into #People from People");
BulkUpload(con, data, "#People");
var results = TransferFromTempToReal(con, "#People", "People", "Name, DOB", "Id");
RunSqlNonQuery(con, "drop table #People"); //not strictly required since this would be removed when the connection's closed as it's session scoped; but best to keep things clean.
}
return results;
}
private void RunSqlNonQuery(SqlConnection con, string sql)
{
using (SqlCommand command = con.CreateCommand())
{
command.CommandText = sql;
command.ExecuteNonQuery();
}
}
private void BulkUpload(SqlConnection con, DataTable data, string targetTable)
{
using(SqlBulkCopy bulkCopy = new SqlBulkCopy(con))
{
bulkCopy.BulkCopyTimeout = 600; //define this in your config
bulkCopy.DestinationTableName = targetTable;
bulkCopy.WriteToServer(data);
}
}
private IEnumerable<long> TransferFromTempToReal(SqlConnection con, string tempTable, string realTable, string columnNames, string idColumnName)
{
using (SqlCommand command = con.CreateCommand())
{
command.CommandText = string.Format("insert into {0} output inserted.{1} select {2} from {3}", realTable, idColumnName, columnNames, tempTable);
using (SqlDataReader reader = command.ExecuteReader())
{
while(reader.Read())
{
yield return r.GetInt64(0);
}
}
}
}
虽然在您的问题中您添加了您不想使用分期 table,因为它是 "overhead"...请尝试。您可能会发现创建暂存的小开销 table 小于使用此方法的性能增益。
显然,它不会像插入和忽略 returned id 那样快;但如果这是你的要求,在没有其他答案的情况下,这可能是最好的选择。
Any idea if somehow, I can get all primary keys back after SqlBulkCopy
operation
你不能。无法直接从 SqlBulkCopy 执行此操作。
PS : I don't want to create any staging table during the process. This
is just an overhead.
不幸的是,如果您想取回主键,您将需要这样做或使用其他方法(如您建议的 TVP)。
免责声明:我是Entity Framework Extensions
的所有者
另一种解决方案是使用已经支持 Entity Framework 的 BulkInsert 的库。在幕后,它使用 SqlBulkCopy + 暂存表。
默认情况下,BulkInsert 方法已经输出了主键值。
图书馆不是免费的,但是,它为您的公司增加了一些灵活性,您将不需要 code/support 任何东西。
示例:
// Easy to use
context.BulkSaveChanges();
// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);
// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);
// Customize Primary Key
context.BulkMerge(customers, operation => {
operation.ColumnPrimaryKeyExpression =
customer => customer.Code;
});
我在向 SQL 服务器插入大量数据时遇到问题。
之前我使用的是 Entity framework,但是对于仅 100K 根级记录(分别包含两个不同的集合,其中每个集合进一步操作大约 200K 条记录)来说速度太慢了 = 大约 500K-600K 条记录在记忆中。 在这里我应用了所有优化(例如 AutoDetectChangesEnabled = false,并在每批之后重新创建和处理上下文。)
我拒绝了该解决方案,并使用了 BulkInsert,它非常快速且高效。只能在一分钟左右插入 100K 条记录。
但主要问题是从新插入的记录中取回主键。为此,我正在考虑编写可以在 TVP 上运行的存储过程(即内存数据table 持有所有根级 100K 记录)。 在里面我会使用 OUTPUT INSERTED.Id 来获取应用程序中的所有主键)。
那么,我如何比较这种方法(即 Sql 在存储过程中插入查询)与 SqlBulkCopy 方法。
知道我是否可以在 SqlBulkCopy 操作后取回所有主键吗?或者关于 OUTPUT Inserted.Id 的具体内容 return 应用程序中所有正确的新密钥。
PS : 我不想在此过程中创建任何暂存 table。这只是开销。
这是一个基于评论中的讨论/扩展此处提到的想法的示例:Possible to get PrimayKey IDs back after a SQL BulkCopy?
即
- 从 C# 批量上传到 SQL 中的临时文件 table
- 使用Sql将数据从临时table复制到实际table(此时生成ID),return ID。
我还没有机会对此进行测试,但希望这会有所帮助:
//using System.Data.SqlClient;
//using System.Collections.Generic;
public DataTable CreatePersonDataTable(IEnumerable<PersonDTO> people)
{
//define the table
var table = new DataTable("People");
table.Columns.Add(new DataColumn("Name", typeof(string)));
table.Columns.Add(new DataColumn("DOB", typeof(DateTime)));
//populate it
foreach (var person in people)
{
table.Rows.Add(person.Name, person.DOB);
}
return table;
}
readonly string ConnectionString; //set this in the constructor
readonly int BulkUploadPeopleTimeoutSeconds = 600; //default; could override in constructor
public IEnumerable<long> BulkUploadPeople(IEnumerable<PersonDTO> people) //you'd want to break this up a bit; for simplicty I've bunged everything into one big method
{
var data = CreatePersonDataTable(people);
using(SqlConnection con = new SqlConnection(ConnectionString))
{
con.Open(); //keep same connection open throughout session
RunSqlNonQuery(con, "select top 0 Name, DOB into #People from People");
BulkUpload(con, data, "#People");
var results = TransferFromTempToReal(con, "#People", "People", "Name, DOB", "Id");
RunSqlNonQuery(con, "drop table #People"); //not strictly required since this would be removed when the connection's closed as it's session scoped; but best to keep things clean.
}
return results;
}
private void RunSqlNonQuery(SqlConnection con, string sql)
{
using (SqlCommand command = con.CreateCommand())
{
command.CommandText = sql;
command.ExecuteNonQuery();
}
}
private void BulkUpload(SqlConnection con, DataTable data, string targetTable)
{
using(SqlBulkCopy bulkCopy = new SqlBulkCopy(con))
{
bulkCopy.BulkCopyTimeout = 600; //define this in your config
bulkCopy.DestinationTableName = targetTable;
bulkCopy.WriteToServer(data);
}
}
private IEnumerable<long> TransferFromTempToReal(SqlConnection con, string tempTable, string realTable, string columnNames, string idColumnName)
{
using (SqlCommand command = con.CreateCommand())
{
command.CommandText = string.Format("insert into {0} output inserted.{1} select {2} from {3}", realTable, idColumnName, columnNames, tempTable);
using (SqlDataReader reader = command.ExecuteReader())
{
while(reader.Read())
{
yield return r.GetInt64(0);
}
}
}
}
虽然在您的问题中您添加了您不想使用分期 table,因为它是 "overhead"...请尝试。您可能会发现创建暂存的小开销 table 小于使用此方法的性能增益。
显然,它不会像插入和忽略 returned id 那样快;但如果这是你的要求,在没有其他答案的情况下,这可能是最好的选择。
Any idea if somehow, I can get all primary keys back after SqlBulkCopy operation
你不能。无法直接从 SqlBulkCopy 执行此操作。
PS : I don't want to create any staging table during the process. This is just an overhead.
不幸的是,如果您想取回主键,您将需要这样做或使用其他方法(如您建议的 TVP)。
免责声明:我是Entity Framework Extensions
的所有者另一种解决方案是使用已经支持 Entity Framework 的 BulkInsert 的库。在幕后,它使用 SqlBulkCopy + 暂存表。
默认情况下,BulkInsert 方法已经输出了主键值。
图书馆不是免费的,但是,它为您的公司增加了一些灵活性,您将不需要 code/support 任何东西。
示例:
// Easy to use
context.BulkSaveChanges();
// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);
// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);
// Customize Primary Key
context.BulkMerge(customers, operation => {
operation.ColumnPrimaryKeyExpression =
customer => customer.Code;
});