2010-09-24 14 views
7

Sto usando SqlBulkCopy su due SQL Server 2008 con diversi set di colonne (andando a spostare alcuni dati dal server prod a dev). Quindi, vuoi saltare alcune colonne non ancora esistenti/non ancora rimosse.Salta alcune colonne in SqlBulkCopy

Come posso farlo? Qualche trucco con ColumnMappings?

Edit:

devo fare:

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) 

e ottengo:

Il dato ColumnMapping non corrisponde a nessuna colonna nella origine o di destinazione.

risposta

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: cool e riutilizzabile. Credo che "stringTarget" e "stringSource" siano nomi di colonne, giusto? – xameeramir

+1

@student: hey, IIRC sono stringhe di connessione rispettivamente ai database di origine e di destinazione. – abatishchev

+0

Fantastico. Avevo bisogno di aggiungere un po 'di codice per gestire correttamente i nomi di schema e database, ma questo è esattamente ciò di cui avevo bisogno. –

1

provare questo: SqlBulkCopyColumnMapping Class

spero che tu sia cercando lo stesso

+0

Sì, io sono parlando esattamente di questa classe, ma come saltare una colonna nel sorgente? '.Add (new SqlDataMapping (" deleted-column-on-target "," ")'?Ovviamente posso rimuoverlo dal sorgente nella query sottostante - 'SELECT a, b, c' invece di' SELECT * '- ma questa non è una soluzione – abatishchev

+1

Se non si vuole copiarlo dall'origine alla destinazione, lasciatelo fuori dalla mappatura. Il mapping copierà solo i dati da colonne specificate. – cjk

9

Quando SqlBulkCopyColumnMapping viene utilizzato, solo le colonne per i quali vengono create mappature verranno copiati.

Se non si crea una mappatura per una colonna, questa verrà ignorata dal processo di copia.

Questo è possibile vedere nel codice demo here - la tabella di esempio nel database demo AdventureWorks contiene più colonne di quelle mappate o copiate.

EDIT

E 'difficile essere certi, senza ulteriori informazioni sullo schema di database, ma a occhio e croce il problema è con questa affermazione:

new SqlBulkCopyColumnMapping(c.ColumnName, c.ColumnName) 

Dalla tua descrizione, sembra che non tutte le colonne nella tabella di origine esistono nella tabella di destinazione. È necessario un filtro nel ciclo di costruzione SqlBulkCopyColumnMapping per saltare le colonne che non esistono nella destinazione.

mio C# non è abbastanza buono per fare un esempio, che sono sicuro lavorerà, ma in pseudocodice sarebbe

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

(io sono sicuro che è possibile convertire questo per un'espressione lambda)

Si noti che questo non è particolarmente robusto nello scenario in cui i nomi delle colonne corrispondono ma i tipi di dati non sono compatibili.

+0

Vedere il mio post aggiornato. Che cosa sto facendo di sbagliato? Probabilmente capisco - la fonte ha una colonna, ma il bersaglio - non farlo. Dovrei confrontare lo schema di origine/destinazione e utilizzare solo le colonne esistenti in entrambi – abatishchev

+0

@abatishchev - aggiunto più dettagli –

+0

Grazie! Hai schiarito la mia visione. Ma il tuo esempio non è adatto purtroppo per me perché non ho una tabella di destinazione, solo il suo nome. Quindi devi chiamare 'sp_Columns' per determinare le colonne della tabella. – abatishchev

2

Ed Harper, questo è ciò che sembra senza pseudo codice (in questo caso da dt DataTable (completamente definito) a una tabella esistente nel db:

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; 
} 
Problemi correlati