C# 使用Sql insert语句插入数据与使用SqlBulkCopy插入数据有什么区别?
我在向SQL server插入大量数据时遇到问题 以前我使用的是实体框架,但对于仅包含两个不同集合的100K根级记录来说,速度非常慢,其中每个集合进一步操作200K记录,大约=内存中的500K-600K记录。 在这里,我应用了所有优化,例如AutoDetectChangesEnabled=false,并在每个批处理之后重新创建和处理上下文 我拒绝了这个解决方案,并使用了BulkInsert,它的速度非常快,效率也非常高。在一分钟左右的时间里插入了10万条记录 但是主要的问题是从新插入的记录中取回主键。为此,我想写一个存储过程,它可以在TVP上运行,即内存中的数据表中保存所有根级别的100K记录。 在那个里,我将使用OUTPUT INSERTED.Id来获取应用程序中的所有主键 那么,我如何比较这种方法,即存储过程内的Sql插入查询和SqlBulkCopy方法呢 不知怎的,我可以在SqlBulkCopy操作后取回所有主键吗?或者关于插入的输出的具体内容。Id将在应用程序中返回所有正确的新键C# 使用Sql insert语句插入数据与使用SqlBulkCopy插入数据有什么区别?,c#,sql-server,entity-framework,bulkinsert,table-valued-parameters,C#,Sql Server,Entity Framework,Bulkinsert,Table Valued Parameters,我在向SQL server插入大量数据时遇到问题 以前我使用的是实体框架,但对于仅包含两个不同集合的100K根级记录来说,速度非常慢,其中每个集合进一步操作200K记录,大约=内存中的500K-600K记录。 在这里,我应用了所有优化,例如AutoDetectChangesEnabled=false,并在每个批处理之后重新创建和处理上下文 我拒绝了这个解决方案,并使用了BulkInsert,它的速度非常快,效率也非常高。在一分钟左右的时间里插入了10万条记录 但是主要的问题是从新插入的记录中取回
PS:我不想在此过程中创建任何临时表。这只是一项开销。以下是一个基于评论中讨论/扩展此处提及想法的示例: i、 e 在SQL中执行从C到临时表的批量上载 使用Sql将数据从临时表复制到生成ID的实际表,并返回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那样快;但如果这是您的要求,在没有其他答案的情况下,这可能是最好的选择。以下是一个基于评论中讨论/扩展此处提及想法的示例: i、 e 在SQL中执行从C到临时表的批量上载 使用Sql将数据从临时表复制到生成ID的实际表,并返回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
免责声明:我是
另一种解决方案是使用已经支持BulkInsert for Entity Framework的库。在后台,它使用SqlBulkCopy+临时表
默认情况下,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;
});
不知怎的,我可以在SqlBulkCopy之后取回所有主键吗
操作
你不能。无法直接从SqlBulkCopy执行此操作
PS:我不想在此过程中创建任何临时表。这
这只是一笔开销
不幸的是,如果你想拿回你的主键,你需要这样做,或者像你建议的那样使用另一种方法TVP
免责声明:我是
另一种解决方案是使用已经支持BulkInsert for Entity Framework的库。在后台,它使用SqlBulkCopy+临时表
默认情况下,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;
});
@JohnLBevan的可能副本:我不想创建任何暂存表,从那里我可以插入到最终表。那是错误的想法。我已经通过了TVP,它与目标表的数据和结构完全相同。@Usman不使用暂存表通常是非常非常糟糕的
主意在ETL中使用EF或任何ORM更糟糕。ETL场景没有业务功能,只有数据转换。@Usman;TVP表值参数与数据库本身中的内容不同。在上面的链接上尝试建议的答案,看看它是否对你有好处;如果没有,请让我们知道。如果你的题目和问题的主体都问同样的问题,那就太好了。在标题中,您似乎想了解两者之间的内部实现差异。在正文中,您似乎特别需要从SQL BulkCopy获取ID—这确实是链接问题的精确副本。那么-这是哪一个?可能是@JohnLBevan的重复:我不想创建任何暂存表,从那里我可以插入到最终表。那是错误的想法。我已经通过了TVP,它与目标表的数据和结构完全相同。@Usman不使用暂存表通常是一个非常非常糟糕的主意。在ETL中使用EF或任何ORM更糟糕。ETL场景没有业务功能,只有数据转换。@Usman;TVP表值参数与数据库本身中的内容不同。在上面的链接上尝试建议的答案,看看它是否对你有好处;如果没有,请让我们知道。如果你的题目和问题的主体都问同样的问题,那就太好了。在标题中,您似乎想了解两者之间的内部实现差异。在正文中,您似乎特别需要从SQL BulkCopy获取ID—这确实是链接问题的精确副本。那么,是哪一个呢?