wtorek, 25 stycznia 2011

[ADO.NET] DataTableMapping i DataColumnMapping

Ostatnio, w ramach przygotowania do egzaminu MCTS z ADO “rozpoznawałem” dokładniej temat środowiska bezpołączeniowego (disconnected environment), czyli DataAdapter’y, DataSet’y, itp. Podczas nauki natknąłem się na temat mapowania nazw tabel i kolumn znajdujących się w zapytaniu SQL pobierającym dane z bazy na nazwy tabel i kolumn znajdujących się w obiekcie typu DataSet. Na pierwszy rzut oka, mogłoby się wydawać, że mając kod, jak poniżej, po wywołaniu metody Fill(), DataSet o nazwie ds będzie posiadał jedną tabelę (czyli obiekt DataTable) o nazwie MyTable:

SqlConnection conn = new SqlConnection("conn str"); 
DbDataAdapter da = new SqlDataAdapter("select * from MyTable", conn); 
DataSet ds = new DataSet(); 
da.Fill(ds); 

Niestety sytuacja nie jest taka prosta – jeśli bowiem w zapytaniu zostanie użyte kilka tabel (np.poprzez użycie klauzuli Join) to DataAdapter nie ma jak się domyślić, której nazwy tabeli użyć. Programiści Microsoftu użyli zatem prostego rozwiązania – pierwsza wypełniana tabela nazywa się po prostu Table, a kolejne Table + <indeks>.

Jak zatem zmienić nazwę tabeli docelowej, do której mają być załadowane dane z zapytania SQL? Jak nie trudno się domyślić, służy do tego wspomniany w tytule obiekt DataTableMapping. W konstruktorze tej klasy podajemy jako pierwszy parametr nazwę źródłową tabeli w obiekcie DataSet (w naszym przykładzie będzie to po prostu Table), a jako drugi parametr podajemy nazwę ma mieć tabela. Możemy także stworzyć obiekt używając domyślnego konstruktora i ustawić własność SourceTable (nazwa tabeli źródłowej) i DataSetTable (nazwa tabeli po przemapowaniu). Następnie tak utworzony obiekt dodajemy jako kolekcji TableMappings DataAdapter’a. I przykład:

SqlConnection conn = new SqlConnection("conn str"); 
DbDataAdapter da = new SqlDataAdapter("select * from MyTable", conn); 
DataSet ds = new DataSet(); 

// Tworzymy mapowanie i dodajemy do DataAdapter'a
DataTableMapping ta = new DataTableMapping("Table", "MyTable");
da.TableMappings.Add(ta);

da.Fill(ds); 

Należy przy tym pamiętać, że jeśli użyta została metoda Fill(), która jako jeden z parametrów ma nazwę tabeli, to w mapowaniu należy użyć tej samej nazwy:

SqlConnection conn = new SqlConnection("conn str"); 
DbDataAdapter da = new SqlDataAdapter("select * from MyTable", conn); 
DataSet ds = new DataSet(); 
// Poniższe mapowanie nie zadziała - tabela źródłowa to w tym 
// przypadku Result, a nie Table:
// DataTableMapping ta = new DataTableMapping("Table", "MyTable");

// Poprawne mapowanie:
DataTableMapping ta = new DataTableMapping("Result", "MyTable");
da.TableMappings.Add(ta);

da.Fill(ds, "Result"); 

Jeśli chodzi o mapowanie kolumn to sprawa jest dużo prostsza. Domyślnie DataAdapter, przy wypełnianiu tabel nazywa je tak samo jak występują w zapytaniu SQL. Jeśli więc chcemy zmienić nazwy kolumn, wystarczy utworzyć obiekt typu DataColumnMapping i jako parametry w konstruktorze podać nazwę kolumny w zapytaniu SQL oraz nazwę pod jaką kolumna ma się pojawić w wypełnianym obiekcie DataTable:

SqlConnection conn = new SqlConnection("conn str"); 
DbDataAdapter da = new SqlDataAdapter("select * from MyTable", conn); 
DataSet ds = new DataSet(); 

DataTableMapping ta = new DataTableMapping("Table", "MyTable");

// Tworzymy nowe mapowanie kolumn i dodajemy do kolekcji 
//    ColumnMappings obiektu DataTableMapping
DataColumnMapping cm = new DataColumnMapping("name", "Full Name");
ta.ColumnMappings.Add(cm);

da.TableMappings.Add(ta);

da.Fill(ds); 

Na koniec jeszcze kwestia mapowań i metody FillSchema. W przypadku tej metody, wszystko zależy od parametru typu SchemaType – mapowania są brane pod uwagę, tylko jeśli parametr ma wartość Mapped.