2009-07-29 8 views
23

Tenemos la necesidad de actualizar varias tablas que tienen relaciones padre/hijo basadas en una clave primaria Identity en la tabla primaria, a la que se refieren una o más tablas secundarias como una clave foráneaSqlBulkCopy y DataTables con relación primario/secundario en la columna de identidad

  • Debido al alto volumen de datos, nos gustaría construir estas tablas en memoria, a continuación, utilizar SqlBulkCopy de C# para actualizar la base de datos en masa, ya sea del conjunto de datos o las tablas de datos individuales.
  • También nos gustaría hacer esto en paralelo, desde múltiples hilos, procesos y posiblemente clientes.

Nuestro prototipo en Fa # muestra una gran promesa, con un aumento de 34x rendimiento, pero esto obliga código conocido valores de identidad de la tabla primaria. Cuando no se fuerza, la columna Identity se genera correctamente en la base de datos cuando SqlBulkCopy inserta las filas, pero los valores de identidad NO se actualizan en la DataTable en memoria. Además, incluso si lo fueran, no está claro si el DataSet corregirá correctamente las relaciones padre/hijo, para que las tablas secundarias puedan escribirse posteriormente con los valores de clave foránea correctos.

Puede alguien explicar cómo tener valores de identidad de actualización SqlBulkCopy y, además, cómo configurar un conjunto de datos con el fin de retener y relaciones actualización de padre/hijo, si esto no se realiza de forma automática cuando un adaptador de datos se llama a FillSchema en las tablas de datos individuales .

respuestas que no estoy Busco:

  • Lea la base de datos para encontrar el valor más alto de identidad actual, y luego incrementar manualmente al crear cada fila padre. No funciona para múltiples procesos/clientes y, como yo entiendo, las transacciones fallidas pueden hacer que algunos valores de Identidad se omitan, por lo que este método podría arruinar la relación.
  • Escriba las filas primarias una a la vez y solicite el valor de Identidad de nuevo. Esto vence al menos algunas de las ganancias al usar SqlBulkCopy (sí, hay muchas más filas secundarias que las de los padres, pero todavía hay muchas filas principales).

similares a la siguiente pregunta sin respuesta:

+0

La cuestión clave es cómo son los registros secundarios relacionados con los registros primarios antes de que sean puestos en las mesas? – RBarryYoung

+0

Puede quitar un bloqueo de tabla en todas las tablas afectadas antes de recuperar el siguiente valor de identidad, esto garantizaría que otros procesos/clientes no puedan alterar sus identificadores generados. De lo contrario, quizás necesite una forma diferente de generar identificadores, como un generador de hilos. –

Respuesta

0

supongo que la compensación que se enfrenta es el rendimiento de la BulkInsert vs el Reliabilty de la identidad.

¿Puede poner la base de datos en SingleUserMode temporalmente para realizar su inserción?

Me enfrenté a un problema muy similar con mi proyecto de conversión donde estoy agregando una columna Identity a tablas muy grandes, y tienen hijos. Afortunadamente, pude configurar la identidad de las fuentes padre e hijo (utilicé un TextDataReader) para realizar BulkInsert, y generé los archivos padre e hijo al mismo tiempo.

también gané el rendimiento gana que está hablando, OleDbDataReader Fuente -> StreamWriter ... y luego TextDataReader -> SQLBulk

9

En primer lugar: SqlBulkCopy no es posible hacer lo que quiera. Como su nombre lo indica, es solo una "calle de sentido único". Muevo los datos al servidor SQL lo más rápido posible. Es el .Versión neta del antiguo comando de copia masiva que importa archivos de texto sin procesar en tablas. De modo que no hay forma de recuperar los valores de identidad si está utilizando SqlBulkCopy.

He realizado un gran volumen de procesamiento de datos y he enfrentado este problema varias veces. La solución depende de su arquitectura y distribución de datos. Aquí están algunas ideas:

  • crear un conjunto de tablas de destino para cada hilo, la importación en estas tablas. Al final únete a estas tablas. La mayor parte de esto se puede implementar de una manera bastante genérica donde se generan tablas llamadas TABLENAME_THREAD_ID automáticamente a partir de tablas llamadas TABLENAME.

  • Mueva la generación de ID completamente fuera de la base de datos. Por ejemplo, implemente un servicio web central que genere los ID. En ese caso, no debe generar un ID por llamada, sino generar rangos de ID. De lo contrario, la sobrecarga de la red suele ser un cuello de botella.

  • Intenta generar IDs con tus datos. Si es posible, tu problema habría desaparecido. No diga "no es posible" ayunar. ¿Quizás pueda usar identificadores de cadena que se pueden limpiar en un paso de procesamiento posterior?

Y una observación más: Un aumento del factor de 34 cuando se utiliza bulkcopy suena a pequeña en la opinión. Si desea insertar datos rápidamente, asegúrese de que su base de datos esté configurada correctamente.

1

La única forma de que pueda hacer lo que desee con SqlBulkCopy es insertar primero los datos en una tabla de etapas. Luego use un procedimiento almacenado para distribuir los datos a las tablas de destino. Sí, esto causará una desaceleración, pero seguirá siendo rápido.

También puede considerar el rediseño de sus datos, es decir, dividirlo, etc. desnormalización que

1

set identity_insert <table> on y dbcc checkident son sus amigos aquí. Esto es algo así como lo que hice en el pasado (ver ejemplo de código). La única advertencia real es que el proceso de actualización es el único que puede insertar datos: todos los demás tienen que salir del grupo mientras se lleva a cabo la actualización. Podría, por supuesto, hacer este tipo de mapeo programáticamente antes de cargar las tablas de producción. Pero se aplica la misma restricción en las inserciones: el proceso de actualización es el único que se puede jugar.

-- 
-- 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. 
------------------------------------ 
4

Lea este artículo. Creo que esto es exactamente lo que estás buscando y más. Muy buena y elegante solución.

http://www.codinghelmet.com/?path=howto/bulk-insert

+0

La solución vinculada es interesante; sin embargo, requiere modificaciones de esquema para el método no EF. No es ideal. –

+0

Esta solución se basa en la idea de que el repositorio y la base de datos deben cumplir los requisitos para la inserción masiva. Muchos desarrolladores olvidan la opción de usar ADO.NET directamente en el repositorio cuando EF ya está en uso. Sin embargo, mezclar las tecnologías y los diseños es un enfoque válido en situaciones en las que el enfoque único no resuelve el problema. La misma sugerencia se puede encontrar en MSDN en esta discusión: http://social.msdn.microsoft.com/Forums/en-US/a0bf232e-4b79-4ab6-b26c-91867754baec/implementing-bulk-save-with-entity- marco de referencia –

Cuestiones relacionadas