2010-03-04 12 views
22

Mam następujący kod, który wypełnia dataTable1 i dataTable2 za pomocą dwóch prostych kwerend SQL, dataTableSqlJoined jest wypełniany z tych samych tabel, ale połączone razem.Utwórz połączone DataTable z dwóch DataTables połączonych z LINQ. C#

Próbuję napisać zapytanie LINQ, które może utworzyć dataTableLinqJoined tak, jakby zostało utworzone przy użyciu SQL. W moim przykładzie poniżej zwraca tylko wartości z dataTable1.

Problemem jest to, co umieścić w SELECT zapytania linq. W jaki sposób mogę utworzyć nowy DataRow zawierający wszystkie kolumny z obu DataRows. Nie będę znać dokładnych nazw kolumn/schematów zapytań do czasu wykonania.

sqlCommand = new SqlCommand("SELECT ID, A, B FROM Table1", sqlConnection, sqlTransaction); 
sqlAdapter = new SqlDataAdapter(sqlCommand); 
DataTable dataTable1 = new DataTable(); 
sqlAdapter.Fill(dataTable1); 

sqlCommand = new SqlCommand("SELECT ID, C, D FROM Table2", sqlConnection, sqlTransaction); 
sqlAdapter = new SqlDataAdapter(sqlCommand); 
DataTable dataTable2 = new DataTable(); 
sqlAdapter.Fill(dataTable2); 

sqlCommand = new SqlCommand("SELECT Table1.ID, A, B, Table2.ID, C, D FROM Table1 INNER JOIN Table2 ON Table1.ID = Table2.ID", sqlConnection, sqlTransaction); 
sqlAdapter = new SqlDataAdapter(sqlCommand); 
DataTable dataTableSqlJoined = new DataTable(); 
sqlAdapter.Fill(dataTableSqlJoined); 

var dataRows = 
    from 
     dataRows1 in dataTable1.AsEnumerable() 
    join 
     dataRows2 in dataTable2.AsEnumerable() 
    on 
     dataRows1.Field<int>("ID") equals dataRows2.Field<int>("ID") 
    select 
     dataRows1; // + dataRows2; 

DataTable dataTableLinqJoined = dataRows.CopyToDataTable(); 

Dla nieco więcej tła, kwerenda złożona jest bardzo intensywnie DB i powoduje problemy z wydajnością. Dane zwrócone przez pierwsze zapytanie są dość statyczne i mogą być silnie zbuforowane. Dane zwracane przez drugie zapytanie zmieniają się stale, ale są szybkie do uruchomienia i dlatego nie muszą być buforowane. Istnieje również wiele kodów zależnych od przejścia połączonej DataTable, a zatem nie ma zbyt wielu dostępnych opcji w przekazywaniu danych w innym formacie.

+0

Jestem nieco ciekawy, jak z jednej strony znasz wydajność tych zapytań, ale z drugiej strony nie znasz struktury tabeli przed uruchomieniem. –

+0

Zapytania są budowane dynamicznie. –

Odpowiedz

20
.

Czy obejrzałeś już tę stronę?

HOW TO: Implement a DataSet JOIN helper class in Visual C# .NET

Jeśli to podejście nie jest LINQy dla ciebie za mało, można wyrwać dane wierszy w macierzy obiektu:

DataTable targetTable = dataTable1.Clone(); 
var dt2Columns = dataTable2.Columns.OfType<DataColumn>().Select(dc => 
    new DataColumn(dc.ColumnName, dc.DataType, dc.Expression, dc.ColumnMapping)); 
targetTable.Columns.AddRange(dt2Columns.ToArray()); 
var rowData = 
    from row1 in dataTable1.AsEnumerable() 
    join row2 in dataTable2.AsEnumerable() 
     on row1.Field<int>("ID") equals row2.Field<int>("ID") 
    select row1.ItemArray.Concat(row2.ItemArray).ToArray(); 
foreach (object[] values in rowData) 
    targetTable.Rows.Add(values); 

Myślę, że to prawie tak lakoniczne, jak masz zamiar być w stanie to zrobić, a wytłumaczę dlaczego: to schemat.

A DataRow nie jest niezależnym obiektem; to zależy od posiadania DataTable i nie może bez niego żyć. Istnieje nieobsługiwany sposób, aby utworzyć "odłączony" DataRow; metoda rozszerzenia CopyToDataTable() działa na wierszach już istniejących w jednym DataTable i po prostu kopiuje schemat ze źródła (pamiętaj, że każde DataRow ma odniesienie do rodzica Table) przed skopiowaniem samych wierszy (najprawdopodobniej za pomocą ImportRow, chociaż nie mam faktycznie otworzyłem Reflector, by sprawdzić).

W tym przypadku masz nowy schemat, który musisz utworzyć.Zanim będzie można utworzyć dowolne (nowe) wiersze, należy utworzyć tabelę, aby je przechowywać najpierw, a to oznacza zapisanie co najmniej 3 linii kodu u góry powyższej metody.

Następnie można w końcu utworzyć wiersze - ale tylko jeden na raz, ponieważ DataTable i skojarzony z nim DataRowCollection nie ujawniają żadnych metod dodawania wielu wierszy naraz. Można oczywiście dodać własną metodę rozszerzenia dla DataRowCollection aby ten „wygląd” ładniejszy:

public static void AddRange(this DataRowCollection rc, 
    IEnumerable<object[]> tuples) 
{ 
    foreach (object[] data in tuples) 
     rc.Add(tuples); 
} 

Następnie można pozbyć się foreach w pierwszej metody i zastąpić go:

targetTable.Rows.AddRange(rowData); 

Chociaż to po prostu poruszanie gadatliwością, a nie jej eliminowanie.

Podsumowując, tak długo, jak pracujesz ze starszą wersją hierarchii klasy DataSet, zawsze będzie trochę tajemnic. Rozszerzenia Linq do DataSet są ładne, ale są tylko rozszerzeniami i nie mogą zmieniać powyższych ograniczeń.

+0

Spojrzałem na tę stronę, ale nie podobało mi się, że nie było LINQy. Twój przykład na tworzenie targetTable jest świetny. Dzięki. –

+0

Wprowadziłem niewielką zmianę do twojego kodu, itemarray musi być Concat zamiast Union. W przeciwnym razie usuwa wszelkie kolumny o identycznych wartościach (wartości null). Poza tym. Działa idealnie! Dziękuję Ci! –

+0

targetTable.Columns.AddRange (dt2Columns.ToArray()); podaje błąd jako ponownie dodawaną kolumnę o tej samej nazwie. – Cannon

0
select new { 
    ID = dataRows1.ID, // no need to select dataRows2.ID, because of JOIN. 
    A = dataRows1.A, 
    B = dataRows1.B, 
    C = dataRows2.C, 
    D = dataRows2.D 
}; 
+0

Nie znam nazw kolumn/schematów do czasu wykonania. Nie ma również "prostego" sposobu konwersji tego typu na DataTable. –

+0

Nie znaleziono tego w pytaniu. Przepraszam. Aby przekonwertować nieznane typy do DataTable, możesz użyć refleksji (lub zrobić szalone szalone rzeczy za pomocą Expression : http://www.infoq.com/articles/expression-compiler) –

1

Przepraszam, jeśli mówię jak idiota.

Myślę, że powinieneś przygotować stół finałowy (ze wszystkimi polami tabeli A & tabela B).
I zamiast używać LINQ, należy połączyć &, a następnie wykonać ForEach na wyniku & wstawić wartość do ostatecznego datatable.

Pseudokod:

dt1.Join (dt2) .gdzie (...) foreach (row => kod do odczytu zawartości anonimowego obiektu & dodać go do finalTable.Rows)

+0

Zrobiłem coś takiego. na przykład uruchamianie połączonej kwerendy, ale zamiast wykonywać połączenia tylko wstawianie wartości null do kolumn z drugiego zapytania. Następnie wystarczy uruchomić drugie zapytanie, aby je wypełnić. +1 za odpowiedź, ale wciąż mam nadzieję na coś bardziej "eleganckiego". –

5

Aaronaught to było świetne. Ale chciałbym dodać kilka ulepszeń do twojego kodu LINQy. Podczas dodawania kolumn z tabeli dataTable2 do Target, istnieje szansa, że ​​kilka kolumn będzie już istniejących w tabeli Target (do której dołączamy). Więc zaczynamy.

DataTable targetTable = dataTable1.Clone(); 
var dt2Columns = dataTable2.Columns.OfType<DataColumn>().Select(dc => 
new DataColumn(dc.ColumnName, dc.DataType, dc.Expression, dc.ColumnMapping)); 
var dt2FinalColumns=from dc in dt2Columns.AsEnumerable() 
        where targetTable.Columns.Contains(dc.ColumnName) == false 
        select dc; 
targetTable.Columns.AddRange(dt2FinalColumns.ToArray()); 
var rowData =from row1 in dataTable1.AsEnumerable() 
      join row2 in dataTable2.AsEnumerable() 
      on row1.Field<int>("ID") equals row2.Field<int>("ID") 
      select row1.ItemArray.Concat(row2.ItemArray.Where(r2=> row1.ItemArray.Contains(r2)==false)).ToArray(); 
foreach (object[] values in rowData) 
targetTable.Rows.Add(values); 

Mam nadzieję, że będzie to pomocne dla facetów takich jak ja.

+0

Co to jest dtcomments? – Cannon

+0

Zmień dtcomments na dt2Columns. Jedno pytanie: jak dołączyć do tabel, jeśli mam więcej niż tabele do holowania, aby się przyłączyć? – Cannon

+0

'var rowData = od row1 w dataTable1.AsEnumerable() przyłączenia Wiersz2 w dataTable2.AsEnumerable() na row1.Field ("id") równy row2.Field ("id") przyłączenia row3 w .AsEnumerable() na ' – suryakiran

Powiązane problemy