LINQ join для возврата динамического списка столбцов - PullRequest
0 голосов
/ 25 января 2019

Я ищу способ вернуть динамический список столбцов из соединения LINQ двух таблиц данных.

Во-первых, это не дубликат. Я уже изучил и выбросил:

C # LINQ список динамически выбирает столбцы из объединенного набора данных

Создание LINQ-выбора из нескольких таблиц

Как выполнить соединение LINQ, которое ведет себя точно так же, как физическое внутреннее соединение базы данных?

(и многие другие)

Вот моя отправная точка:

public static DataTable JoinDataTables(DataTable dt1, DataTable dt2, string table1KeyField, string table2KeyField, string[] columns) {
   DataTable result = ( from dataRows1 in dt1.AsEnumerable()
                        join dataRows2 in dt2.AsEnumerable()
                        on dataRows1.Field<string>(table1KeyField) equals dataRows2.Field<string>(table2KeyField)
                        [...I NEED HELP HERE with the SELECT....]).CopyToDataTable();
   return result;
}

Несколько замечаний и требований:

  1. Нет движка базы данных. Источниками данных являются большие файлы CSV (500K + записи), которые читаются в c # DataTable s.
  2. Поскольку значения CSV велики, циклически проходить по каждой записи в соединении является плохим решением по соображениям производительности. Я уже пробовал цикл записи, и он слишком медленный. Я получаю отличную производительность при вышеописанном соединении, но не могу найти способ, чтобы оно возвращало только те столбцы, которые я хочу (указанные вызывающей стороной) без циклических записей.
  3. Если мне нужно перебрать столбцы в соединении, это прекрасно, я просто не хочу зацикливать строки.
  4. Я хочу иметь возможность передать массив имен столбцов и вернуть только эти столбцы в результирующем DataTable. Если обе передаваемые таблицы данных имеют одинаковый столбец, и если этот столбец находится в моем массиве имен столбцов, просто передайте обратно любой столбец, потому что в этом случае данные между двумя столбцами будут одинаковыми.
  5. Если мне нужно передать 2 массива (1 для каждого желаемого столбца таблицы данных), это нормально, но 1 массив имен столбцов будет идеальным.
  6. Список столбцов не может быть статическим и жестко закодированным в функцию. Причина в том, что мой JoinDataTables() вызывается из разных мест в моей системе, чтобы присоединиться к широкому кругу CSV-файлов, превращаемых в данные, и каждый CSV-файл имеет очень разные столбцы.
  7. Я не хочу, чтобы все столбцы возвращались в результирующем DataTable - только столбцы, которые я указываю в массиве columns.

Предположим, что перед вызовом JoinDataTables() у меня есть 2 таблицы данных:

Table: T1
T1A T1B T1C T1D
==================
10  AA  H1  Foo1
11  AB  H1  Foo2
12  AA  H2  Foo1
13  AB  H2  Foo2

Table: T2
T2A T2X T2Y T2Z
==================
12  N1  O1  Yeah1
17  N2  O2  Yeah2
18  N3  O1  Yeah1
19  N4  O2  Yeah2

Теперь предположим, что мы объединяем эти 2 таблицы следующим образом: ON T1.T1A = T2.T2A

select * from [join]

и это дает этот набор результатов:

T1A T1B T1C T1D   T2A T2X T2Y T2Z
====================================
12  AA  H2  Foo1  12  N1  O1  Yeah1

Обратите внимание, что объединение дает только 1 строку.

Теперь к сути моего вопроса. Предположим, что для данного варианта использования я хочу вернуть только 4 столбца из этого соединения: T1A, T1D, T2A и T2Y. Поэтому мой набор результатов будет выглядеть так:

T1A T1D   T2A  T2Y
==================
12  Foo1  12   O1

Я хотел бы иметь возможность вызывать мою JoinDataTables функцию следующим образом:

DataTable dt = JoinDataTables(dt1, dt2, "T1A", "T2A", new string[] {"T1A", "T1D", "T2A", "T2Y"});

С учетом производительности и того факта, что я не хочу перебирать записи (потому что это медленно для больших наборов данных), как это можно сделать? (Объединение уже работает хорошо, теперь мне просто нужен правильный select сегмент (либо через new{..}, либо как вы думаете)).

Я не могу принять решение с жестко закодированным списком столбцов внутри функции. Я нашел примеры такого подхода по всему SO.

Есть идеи?

РЕДАКТИРОВАТЬ: Я буду в порядке, возвращая ВСЕ столбцы каждый раз, но каждая попытка включить все столбцы приводила к некоторому типу FULL OUTER JOIN или CROSS JOIN, возвращая на порядки больше записей чем следует. Итак, я был бы готов вернуть все столбцы, если не получу перекрестное соединение.

1 Ответ

0 голосов
/ 25 января 2019

Я не уверен в производительности с 500k записей, но вот попытка решения.

Поскольку вы комбинируете два подмножества DataRow s из разных таблиц, не существует простых операций, которые создадут подмножество или создадут новый DataTable из подмножеств (хотя у меня есть метод расширения для выравнивания IEnumerable<anon> где anon = new { DataRow1, DataRow2, ... } из объединения, это, вероятно, будет медленным для вас).

Вместо этого я предварительно создаю ответ DataTable с запрошенными столбцами, а затем использую LINQ для построения массивов значений, которые будут добавлены в виде строк.

public static DataTable JoinDataTables(DataTable dt1, DataTable dt2, string table1KeyField, string table2KeyField, string[] columns) {
    var rtnCols1 = dt1.Columns.Cast<DataColumn>().Where(dc => columns.Contains(dc.ColumnName)).ToList();
    var rc1 = rtnCols1.Select(dc => dc.ColumnName).ToList();
    var rtnCols2 = dt2.Columns.Cast<DataColumn>().Where(dc => columns.Contains(dc.ColumnName) && !rc1.Contains(dc.ColumnName)).ToList();
    var rc2 = rtnCols2.Select(dc => dc.ColumnName).ToList();

    var work = from dataRows1 in dt1.AsEnumerable()
               join dataRows2 in dt2.AsEnumerable()
               on dataRows1.Field<string>(table1KeyField) equals dataRows2.Field<string>(table2KeyField)
               select (from c1 in rc1 select dataRows1[c1]).Concat(from c2 in rc2 select dataRows2[c2]).ToArray();

    var result = new DataTable();
    foreach (var rc in rtnCols1)
        result.Columns.Add(rc.ColumnName, rc.DataType);
    foreach (var rc in rtnCols2)
        result.Columns.Add(rc.ColumnName, rc.DataType);

    foreach (var rowVals in work)
        result.Rows.Add(rowVals);

    return result;
}

Так как вы использовали синтаксис запроса, я тоже это сделал, но обычно я, вероятно, сделал бы select примерно так:

select rc1.Select(c1 => dataRows1[c1]).Concat(rc2.Select(c2 => dataRows2[c2])).ToArray();

Обновлено: вероятно, стоит использовать порядковые номера столбцов вместо имен для индексации каждого DataRow, заменив определения rc1 и rc2:

var rc1 = rtnCols1.Select(dc => dc.Ordinal).ToList();
var rc1Names = rtnCols1.Select(dc => dc.ColumnName).ToHashSet();
var rtnCols2 = dt2.Columns.Cast<DataColumn>().Where(dc => columns.Contains(dc.ColumnName) && !rc1Names.Contains(dc.ColumnName)).ToList();
var rc2 = rtnCols2.Select(dc => dc.Ordinal).ToList();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...