Создайте комбинированный DataTable из двух таблиц данных, связанных с LINQ. С#
У меня есть следующий код, который заполняет 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, и поэтому для передачи данных в другом формате доступно не так много возможных вариантов.
Ответы
Ответ 1
Вы еще не просмотрели эту страницу?
КАК: Реализовать вспомогательный класс DataSet JOIN в Visual С#.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 для проверки).
В этом случае у вас есть новая схема, которую вы должны создать. Прежде чем вы сможете создавать любые (новые) строки, вам нужно создать таблицу, чтобы удерживать их в первую очередь, а это означает запись по крайней мере трех строк кода в верхней части вышеописанного метода.
Затем вы можете, наконец, создать строки - но только по одному, так как 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 хороши, но они являются только расширениями и не могут изменять ограничения выше.
Ответ 2
Aaronaught это было здорово. Но хотелось бы добавить несколько улучшений в ваш код LINQy. Добавляя столбцы из таблицы данныхTable2 в Target, вероятность того, что в таблице Target (в которой мы присоединяемся) уже есть несколько столбцов. Итак, идем.
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);
Надеюсь, это было бы полезно для таких парней, как я.
Ответ 3
Простите меня, если я буду похож на идиота.
Я думаю, вы должны подготовить финальную таблицу (со всеми полями таблицы A и таблицы B).
И вместо того, чтобы использовать LINQ, выполните объединение, а затем выполните ForEach
в результате и вставьте значение в окончательный файл данных.
Псевдокод:
dt1.Join(dt2).Where(...). ForEach (строка = > код для чтения содержимого анонимного объекта и добавления его в finalTable.Rows)
Ответ 4
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
};