C# 标识列上具有父/子关系的SqlBulkCopy和DataTables
我们需要根据父表中的标识主键(一个或多个子表将其称为外键)更新几个具有父/子关系的表C# 标识列上具有父/子关系的SqlBulkCopy和DataTables,c#,.net,sql-server,sqlbulkcopy,C#,.net,Sql Server,Sqlbulkcopy,我们需要根据父表中的标识主键(一个或多个子表将其称为外键)更新几个具有父/子关系的表 由于数据量很大,我们希望在内存中构建这些表,然后使用SqlBulkCopy from C#从数据集或单个数据表大规模更新数据库 我们还希望从多个线程、进程以及可能的客户机并行执行此操作 我们在F#中的原型显示了很多希望,性能提高了34倍,但这段代码强制父表中的已知标识值。如果不强制,则当SqlBulkCopy插入行时,会在数据库中正确生成标识列,但不会在内存数据表中更新标识值。此外,即使是这样,也不清楚数据
- 由于数据量很大,我们希望在内存中构建这些表,然后使用SqlBulkCopy from C#从数据集或单个数据表大规模更新数据库
- 我们还希望从多个线程、进程以及可能的客户机并行执行此操作
- 读取数据库以查找当前的最高标识值,然后在创建每个父行时手动递增该值。不适用于多个进程/客户端,据我所知,失败的事务可能会导致跳过某些标识值,因此此方法可能会破坏关系
- 逐个写入父行,并要求返回标识值。这至少挫败了使用SqlBulkCopy所获得的一些好处(是的,子行比父行多得多,但父行仍然很多)
我还获得了您所说的性能提升,OleDBDataReader Source->StreamWriter。。。然后是TextDataReader->SQLBulk首先:SqlBulkCopy不可能执行您想要的操作。顾名思义,这只是一条“单行道”。我会尽快将数据移动到sql server中。它是旧的大容量复制命令的.Net版本,用于将原始文本文件导入表中。因此,如果使用SqlBulkCopy,则无法恢复标识值 我已经做了大量的批量数据处理,并多次面临这个问题。解决方案取决于您的体系结构和数据分布。以下是一些想法:
- 为每个线程创建一组目标表,并在这些表中导入。最后,连接这些表。其中大部分可以以一种非常通用的方式实现,即从名为TABLENAME的表自动生成名为TABLENAME\u THREAD\u ID的表
- 将ID生成完全移出数据库。例如,实现一个生成ID的中央Web服务。在这种情况下,您不应该为每个调用生成一个ID,而是生成ID范围。否则,网络开销通常会成为瓶颈
- 尝试从数据中生成ID。如果可能的话,你的问题早就解决了。不要说“这是不可能的”,以快速。也许您可以使用可以在后期处理步骤中清理的字符串ID
还有一句话:当使用BulkCopy声音时,会增加系数34。如果要快速插入数据,请确保数据库配置正确。使用SqlBulkCopy执行所需操作的唯一方法是首先将数据插入临时表。然后使用存储过程将数据分发到目标表。是的,这将导致经济放缓,但仍将很快
你也可以考虑重新设计你的数据,即拆分它,对它进行去格式化等等。
< P> <代码> SETIVITYTIX插入在和<代码> DCBCC Currutys这里是你的朋友。这与我过去所做的类似(参见代码示例)。唯一真正需要注意的是,更新过程是唯一可以插入数据的过程:在更新过程中,其他所有人都必须离开池。当然,您可以在加载生产表之前以编程方式进行这种映射。但同样的限制也适用于插入:更新过程是唯一可以发挥作用的过程--
-- start with a source schema -- doesn't actually need to be SQL tables
-- but from the standpoint of demonstration, it makes it easier
--
create table source.parent
(
id int not null primary key ,
data varchar(32) not null ,
)
create table source.child
(
id int not null primary key ,
data varchar(32) not null ,
parent_id int not null foreign key references source.parent(id) ,
)
--
-- On the receiving end, you need to create staging tables.
-- You'll notice that while there are primary keys defined,
-- there are no foreign key constraints. Depending on the
-- cleanliness of your data, you might even get rid of the
-- primary key definitions (though you'll need to add
-- some sort of processing to clean the data one way or
-- another, obviously).
--
-- and, depending context, these could even be temp tables
--
create table stage.parent
(
id int not null primary key ,
data varchar(32) not null ,
)
create table stage.child
(
id int not null primary key ,
data varchar(32) not null ,
parent_id int not null ,
)
--
-- and of course, the final destination tables already exist,
-- complete with identity properties, etc.
--
create table dbo.parent
(
id int not null identity(1,1) primary key ,
data varchar(32) not null ,
)
create table dbo.child
(
id int not null identity(1,1) primary key ,
data varchar(32) not null ,
parent_id int not null foreign key references dbo.parent(id) ,
)
-----------------------------------------------------------------------
-- so, you BCP or otherwise load your staging tables with the new data
-- frome the source tables. How this happens is left as an exercise for
-- the reader. We'll just assume that some sort of magic happens to
-- make it so. Don't forget to truncate the staging tables prior to
-- loading them with data.
-----------------------------------------------------------------------
-------------------------------------------------------------------------
-- Now we get to work to populate the production tables with the new data
--
-- First we need a map to let us create the new identity values.
-------------------------------------------------------------------------
drop table #parent_map
create table #parent_map
(
old_id int not null primary key nonclustered ,
offset int not null identity(1,1) unique clustered ,
new_id int null ,
)
create table #child_map
(
old_id int not null primary key nonclustered ,
offset int not null identity(1,1) unique clustered ,
new_id int null ,
)
insert #parent_map ( old_id ) select id from stage.parent
insert #child_map ( old_id ) select id from stage.child
-------------------------------------------------------------------------------
-- now that we've got the map, we can blast the data into the production tables
-------------------------------------------------------------------------------
--
-- compute the new ID values
--
update #parent_map set new_id = offset + ( select max(id) from dbo.parent )
--
-- blast it into the parent table, turning on identity_insert
--
set identity_insert dbo.parent on
insert dbo.parent (id,data)
select id = map.new_id ,
data = staging.data
from stage.parent staging
join #parent_map map on map.old_id = staging.id
set identity_insert dbo.parent off
--
-- reseed the identity properties high water mark
--
dbcc checkident dbo.parent , reseed
--
-- compute the new ID values
--
update #child_map set new_id = offset + ( select max(id) from dbo.child )
--
-- blast it into the child table, turning on identity_insert
--
set identity_insert dbo.child on
insert dbo.child ( id , data , parent_id )
select id = parent.new_id ,
data = staging.data ,
parent_id = parent.new_id
from stage.child staging
join #child_map map on map.old_id = staging.id
join #parent_map parent on parent.old_id = staging.parent_id
set identity_insert dbo.child off
--
-- reseed the identity properties high water mark
--
dbcc checkident dbo.child , reseed
------------------------------------
-- That's about all there is too it.
------------------------------------
阅读这篇文章。我想这正是你想要的。非常好和优雅的解决方案
关键问题是,在将子记录放入表之前,它们与父记录的关系如何?在获取下一个标识值之前,您可以在所有受影响的表上设置表锁,这将确保其他进程/客户端无法更改您生成的ID。否则,您可能需要一种不同的方法来生成ID,例如hilo生成器。链接的解决方案很有趣,但是它需要对非EF方法进行模式修改。不理想。此解决方案基于存储库和数据库应符合批量插入要求的思想。当EF已经在使用时,许多开发人员忘记了在存储库中直接使用ADO.NET的选项。然而,混合