2010-09-24 34 views
7

Estoy usando SqlBulkCopy contra dos SQL Server 2008 con diferentes conjuntos de columnas (va a mover algunos datos del servidor prod al dev). Así que quiero omitir algunas columnas que aún no existen/aún no se eliminaron.Omitir algunas columnas en SqlBulkCopy

¿Cómo puedo hacer eso? Algún truco con ColumnMappings?

Editar:

es el siguiente paso:

DataTable table = new DataTable(); 
using (var adapter = new SqlDataAdapter(sourceCommand)) 
{ 
    adapter.Fill(table); 
} 

table.Columns 
    .OfType<DataColumn>() 
    .ForEach(c => bulk.ColumnMappings.Add(
     new SqlBulkCopyColumnMapping(c.ColumnName, c.ColumnName))); 

bulk.WriteToServer(table) 

y sale:

El ColumnMapping dado no coincide con ninguna columna en el origen o destino.

Respuesta

15
DataTable table = new DataTable(); 
using (var adapter = new SqlDataAdapter(sourceCommand)) 
{ 
    adapter.Fill(table); 
} 

using (SqlBulkCopy bulk = new SqlBulkCopy(targetConnection, SqlBulkCopyOptions.KeepIdentity, null) { DestinationTableName = tableName }) 
{ 
    foreach (string columnName in GetMapping(stringSource, stringTarget, tableName)) 
    { 
     bulk.ColumnMappings.Add(new SqlBulkCopyColumnMapping(columnName, columnName)); 
    } 

    targetConnection.Open(); 
    bulk.WriteToServer(table); 
} 

private static IEnumerable<string> GetMapping(string stringSource, string stringTarget, string tableName) 
{ 
    return Enumerable.Intersect(
     GetSchema(stringSource, tableName), 
     GetSchema(stringTarget, tableName), 
     StringComparer.Ordinal); // or StringComparer.OrdinalIgnoreCase 
} 

private static IEnumerable<string> GetSchema(string connectionString, string tableName) 
{ 
    using (SqlConnection connection = new SqlConnection(connectionString)) 
    using (SqlCommand command = connection.CreateCommand()) 
    { 
     command.CommandText = "sp_Columns"; 
     command.CommandType = CommandType.StoredProcedure; 

     command.Parameters.Add("@table_name", SqlDbType.NVarChar, 384).Value = tableName; 

     connection.Open(); 
     using (var reader = command.ExecuteReader()) 
     { 
      while (reader.Read()) 
      { 
       yield return (string)reader["column_name"]; 
      } 
     } 
    } 
} 
+0

@abtishchev - genial y reutilizable. Creo que 'stringTarget' y 'stringSource' son nombres de columna, ¿verdad? – xameeramir

+1

@student: hey, IIRC estas son cadenas de conexión para las bases de datos de origen y destino, respectivamente. – abatishchev

+0

Fantástico. Necesitaba agregar un poco de código para manejar correctamente los nombres de esquema y base de datos, pero esto es exactamente lo que necesitaba. –

1

probar esto: SqlBulkCopyColumnMapping Class

esperamos que están buscando el mismo

+0

Sí, estoy hablando exactamente de esta clase. Pero, ¿cómo omitir una columna en la fuente? '.Add (nueva SqlDataMapping (" deleted-column-on-target "," ")'?Por supuesto que puedo eliminarlo del código fuente en la consulta subyacente - 'SELECCIONE a, b, c' en lugar de' SELECT * '- pero esto no es una solución – abatishchev

+1

Si no desea copiarlo desde el origen al destino, simplemente déjalo fuera del mapeo. La asignación solo copiará los datos de las columnas especificadas. – cjk

9

Cuando se utiliza SqlBulkCopyColumnMapping, sólo las columnas para los que se crearon las asignaciones serán copiados.

Si no crea una asignación para una columna, será ignorado por el proceso de copia.

Puede ver esto en el código de demostración here - la tabla fuente de muestra en la base de datos de demostración de AdventureWorks contiene más columnas de las que se asignan o copian.

EDITAR

Es difícil estar seguro, sin más información acerca del esquema de base de datos, pero a ojo el problema es con esta declaración:

new SqlBulkCopyColumnMapping(c.ColumnName, c.ColumnName) 

Desde su descripción, parece que no se todas las columnas en la tabla fuente existen en la tabla de destino. Necesita un filtro en su ciclo de construcción SqlBulkCopyColumnMapping para omitir cualquier columna que no exista en el destino.

Mi C# no es lo suficientemente bueno para dar un ejemplo de lo que estoy seguro va a funcionar, pero en pseudocódigo sería

foreach column c in sourcetable 
{ 
    if c.ColumnName exists in destination_table.columns 
    { 
      new SqlBulkCopyColumnMapping(c.ColumnName, c.ColumnName) 
    } 
} 

(estoy seguro de que es posible convertir esto en una expresión lambda)

Tenga en cuenta que esto no es particularmente robusto en el escenario donde los nombres de columna coinciden pero los tipos de datos son incompatibles.

+0

Ver mi publicación actualizada. ¿Qué estoy haciendo mal? Probablemente entiendo - la fuente tiene una columna, pero el objetivo - no. Debería comparar el esquema fuente/destino y usar solo las columnas existentes en ambos – abatishchev

+0

@abatishchev - agregó más detalles –

+0

¡Gracias! Usted aclaró mi visión. Pero desafortunadamente su ejemplo no es adecuado para mí porque no tengo una tabla de objetivos, solo su nombre. Entonces, debe llamar a 'sp_Columns' para determinar las columnas de la tabla. – abatishchev

2

Ed Harper, esto es lo que parece, no hay código de pseudo (en este caso de DataTable dt (totalmente definido) a una tabla existente en el PP:

using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) 
{ 
    bulkCopy.DestinationTableName = "dbo.DepartmentsItems"; 

    // Write from the source to the destination. 
    foreach (DataColumn c in dt.Columns) 
    { 
     bulkCopy.ColumnMappings.Add(c.ColumnName, c.ColumnName); 
    } 

    bulkCopy.WriteToServer(dt); 
    return dt.Rows.Count; 
} 
Cuestiones relacionadas