Создайте объединенную таблицу данных из двух таблиц данных, объединенных с помощью LINQ. C # - PullRequest
22 голосов
/ 04 марта 2010

У меня есть следующий код, который заполняет dataTable1 и dataTable2 двумя простыми запросами SQL, dataTableSqlJoined заполняется из тех же таблиц, но объединяется.

Я пытаюсь написать запрос LINQ, который может создать dataTableLinqJoined, как если бы он был создан с использованием SQL. В моем примере ниже он возвращает только значения из dataTable1.

У меня проблема в том, что поставить в SELECT запроса linq. Как я могу создать новый DataRow, содержащий все столбцы из обоих DataRows. Я не буду знать точные имена столбцов / схемы запросов до времени выполнения.

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();

Для получения дополнительной информации объединенный запрос очень интенсивно использует БД и вызывает проблемы с производительностью. Данные, возвращаемые первым запросом, довольно статичны и могут быть сильно кэшированы. Данные, возвращаемые вторым запросом, постоянно изменяются, но они быстро запускаются и поэтому не нуждаются в кэшировании. Существует также много кода, зависящего от передачи объединенной DataTable, и, следовательно, существует не так много возможных вариантов передачи данных в другом формате.

Ответы [ 4 ]

20 голосов
/ 04 марта 2010

Вы уже смотрели эту страницу?

КАК: реализовать вспомогательный класс DataSet JOIN в Visual C # .NET

Если вам не подходит этот подход LINQy, вы можете разбить данные строки на массивы объектов:

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);

Я думаю, что это примерно так же кратко, как вы сможете это сделать, и я объясню почему: это схема.

A DataRow не является самостоятельным объектом; оно зависит от его владения DataTable и не может жить без него. нет поддерживаемого способа для создания "отключенного" DataRow; метод расширения CopyToDataTable() работает со строками, которые уже существуют в одном DataTable, и просто копирует схему из источника (помните, что каждый DataRow имеет ссылку на своего родителя Table) перед копированием самих строк (скорее всего используя ImportRow, хотя я на самом деле не открыл Reflector для проверки).

В этом случае у вас есть новая схема, которую вам нужно создать. Прежде чем вы сможете создать какие-либо (новые) строки, вам нужно создать таблицу, в которой они будут храниться first , и это означает написание как минимум 3 строк кода в верхней части метода, описанного выше.

Затем вы можете, наконец, создать строки - но только по одной за раз, поскольку DataTable и связанные с ним DataRowCollection не предоставляют никаких методов для добавления нескольких строк одновременно. Вы можете, конечно, добавить свой собственный метод расширения для DataRowCollection, чтобы сделать этот «внешний вид» более приятным:

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

Тогда вы можете избавиться от foreach в первом методе и заменить его на:

targetTable.Rows.AddRange(rowData);

Хотя это на самом деле просто движет многословием, а не устраняет его.

Итог, пока вы работаете с унаследованной иерархией классов DataSet, всегда будет небольшая путаница. Расширения Linq to DataSet хороши, но они являются только расширениями и не могут изменить вышеуказанные ограничения.

5 голосов
/ 10 марта 2011

Аарона, это было здорово. Но хотелось бы добавить несколько улучшений в ваш код LINQy. При добавлении столбцов из dataTable2 в целевую таблицу, существует вероятность того, что в целевой таблице уже есть несколько столбцов (к которым мы присоединяемся). Итак, поехали.

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);

Надеюсь, это будет полезно для таких парней, как я.

1 голос
/ 04 марта 2010

Прошу прощения, если я звучу как идиот.

Я думаю, у вас должен быть готов финальный стол (со всеми полями таблицы A и таблицы B).
И вместо использования LINQ выполните объединение, а затем выполните ForEach для результата и вставьте значение в окончательный набор данных.

Псевдокод :

dt1.Join (dt2) .Where (...). ForEach (row => код для чтения содержимого анонимного объекта и добавления его в finalTable.Rows)

0 голосов
/ 04 марта 2010
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 
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...