2010-09-24 18 views
7

Używam SqlBulkCopy przeciwko dwóm SQL Server 2008 z różnymi zestawami kolumn (przeniesienie niektórych danych z serwera prod do dev). Więc chcesz pominąć niektóre kolumny, które jeszcze nie istniały/jeszcze nie zostały usunięte.Pomiń niektóre kolumny w SqlBulkCopy

Jak mogę to zrobić? Trochę sztuczki z ColumnMappings?

Edit:

I dalej:

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) 

a otrzymasz:

Podana ColumnMapping nie zgadza się z dowolnej kolumny w źródle lub przeznaczenia.

Odpowiedz

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 - fajne i wielokrotnego użytku. Wierzę, że "stringTarget" i "stringSource" są nazwami kolumn, prawda? – xameeramir

+1

@student: hej, IIRC są to ciągi połączenia odpowiednio z bazą źródłową i docelową. – abatishchev

+0

Fantastyczne. Musiałem dodać trochę kodu, aby poprawnie obsługiwać nazwy schematów i baz danych, ale jest to dokładnie to, czego potrzebowałem. –

1

spróbuj tego: SqlBulkCopyColumnMapping Class

Nadzieję szukasz tego samego

+0

Tak, jestem mówiąc o dokładnie tej klasie, ale jak pominąć kolumnę w źródle? '.Add (nowe SqlDataMapping (" usunięta kolumna na cel "," ")'?Oczywiście mogę go usunąć ze źródła w zapytaniu bazowym - 'SELECT a, b, c' zamiast' SELECT * '- ale to nie jest rozwiązanie – abatishchev

+1

Jeśli nie chcesz skopiować go ze źródła do miejsca docelowego, po prostu zostaw to poza mapowaniem. Mapowanie będzie tylko kopiować dane z określonych kolumn. – cjk

9

Kiedy SqlBulkCopyColumnMapping jest używana, tylko kolumny, dla których zostały utworzone odwzorowania zostaną skopiowane.

Jeśli nie utworzysz odwzorowania dla kolumny, zostanie ona zignorowana przez proces kopiowania.

Można to zobaczyć w kodzie demo: here - tabela źródłowa próbki w bazie danych AdventureWorks zawiera więcej kolumn niż mapowanych lub kopiowanych.

EDIT

Trudno mieć pewność, bez dalszych informacji o schemacie bazy danych, ale w przypuszczeniu, że problem jest z tym stwierdzeniem:

new SqlBulkCopyColumnMapping(c.ColumnName, c.ColumnName) 

Z Twojego opisu brzmi jak nie wszystkie kolumny w tabeli źródłowej istnieją w tabeli docelowej. Potrzebujesz filtra w pętli konstrukcyjnej SqlBulkCopyColumnMapping, aby pominąć kolumny, które nie istnieją w miejscu docelowym.

Moje C# nie jest wystarczająco dobry, aby dać przykład, który jestem przekonany, będzie działać, ale w Pseudokod byłoby

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

(jestem pewien, że to możliwe, aby przekształcić to do wyrażenia lambda)

Należy zauważyć, że nie jest to szczególnie odporne w scenariuszu, w którym nazwy kolumn są zgodne, ale typy danych są niekompatybilne.

+0

Zobacz mój zaktualizowany wpis. Co ja robię źle? Prawdopodobnie rozumiem - źródło ma kolumnę, ale cel - nie. Powinienem porównać schemat źródło/cel i użyć tylko kolumn istnieje zarówno w – abatishchev

+0

@abatishchev - dodał więcej szczegółów –

+0

Dzięki! Oczyściłeś moją wizję. Ale twój przykład nie jest dla mnie niestety odpowiedni, ponieważ nie mam tabeli docelowej, tylko jej nazwę. Musimy więc wywołać 'sp_Columns', aby określić kolumny tabeli. – abatishchev

2

Ed Harper, to jak to wygląda bez kodu pseudo (w tym przypadku z DataTable dt (w pełni zdefiniowane) do istniejącej tabeli w 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; 
} 
Powiązane problemy