[英]What is the difference between inserting data using Sql insert statements and SqlBulkCopy?
我有向SQL Server插入大量数据的问题。
以前我使用的是Entity Framework,但是对于仅100K根级别的记录(分别包含两个不同的集合,其中每个大约进一步对200K记录进行操作)的速度真是太慢了=内存中的记录约为500K-600K。 在这里,我应用了所有优化(例如AutoDetectChangesEnabled = false,并在每次批处理之后重新创建和布置上下文。)
我拒绝了该解决方案,并使用了非常快且非常高效的BulkInsert。 一分钟左右就可以插入10万条记录。
但是主要的问题是从新插入的记录取回主键。 为此,我正在考虑编写可以在TVP上运行的存储过程(即在存储所有根级100K记录的内存数据表中)。 在里面,我将使用OUTPUT INSERTED.Id来获取应用程序内部的所有主键)。
因此,如何将这种方法(即存储过程中的Sql Insert查询)与SqlBulkCopy方法进行比较。
任何想法,如果我能在SqlBulkCopy操作之后找回所有主键? 或关于OUTPUT Inserted.Id的具体说明将返回应用程序中所有正确的新键。
PS:在此过程中,我不想创建任何登台表。 这只是开销。
这是一个基于注释中讨论的示例/扩展了此处提到的想法: 在SQL BulkCopy之后可以获取PrimayKey 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);
}
}
}
}
在您的问题中,您已经添加了您不想使用登台表的信息,因为它是“开销”的……请尝试。 您可能会发现,创建登台表的少量开销小于使用此方法所带来的性能提升。
显然,它的速度不会像插入和忽略返回的ID那样快。 但这是您的要求,如果没有其他答案,这可能是最好的选择。
任何想法,如果以某种方式,我可以在SqlBulkCopy操作之后取回所有主键
你不能。 没有办法直接从SqlBulkCopy进行操作。
PS:在此过程中,我不想创建任何登台表。 这只是开销。
不幸的是,如果您想找回主键,则需要这样做或使用其他方法(如您建议的TVP)。
免责声明 :我是Entity Framework Extensions的所有者
一种替代解决方案是使用已经支持BulkInsert for Entity Framework的库。 在后台,它使用SqlBulkCopy + Staging Tables。
默认情况下,BulkInsert方法已经输出主键值。
该库不是免费的,但是,它为您的公司增加了一些灵活性,并且您无需编写任何代码/支持任何内容。
例:
// 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;
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.