greenhorn_dev Ответов: 2

Запрос объекта DataTable с динамическими именами столбцов с применением технологии LINQ


Я предварю этот вопрос сообщением о том, что прочитал сайт MSDN, связанный с LINQ (который ориентирован на использование баз данных с известными именами столбцов, а не наборов данных и таблиц данных в памяти), и потратил, возможно, 20 часов на поиск решения на различных форумах для того, что я должен решить.

У меня есть с# Приложение Winforms, которое будет принимать CSV-файл и загружать данные в datatable. Имена столбцов datatable считываются и заполняются в другой datatable, который служит для сопоставления исходного datatable с нормализованным конечным datatable. Пользователю предоставляется экран, на котором он вводит данные о том, какой исходный столбец соответствует какому целевому столбцу.

Проблема
Как скопировать только столбцы из исходной таблицы, необходимые для целевой таблицы, используя таблицу сопоставления, чтобы сказать, что должно идти куда. В SQL я бы сделал это с помощью динамического SQL, например:

DECLARE @sql nvarchar(4000)

-- This creates the dynamic query statement and embeds the concatenated list of source column names (the STUFF function handles the concatenation) mapped to the target columns or puts in an empty string if the column was not mapped to avoid a column count mismatch when inserting
SELECT @sql = 'SELECT ' + STUFF((SELECT ',' + ISNULL(m.src,'') FROM tbl_map m FOR XML PATH('')),1,1,'') + ' FROM tbl_src'

-- This inserts the records from the source table using the dynamic query into the target table
INSERT INTO target_table
EXEC(@sql)


Опять же, ничто из того, что я где-либо читал, не касалось использования datatable в качестве источника, где имена столбцов заранее не известны.

Источник Образца:
-----------------------------------------------------------------------
| FName    | LName   | DOB      |  SSN      | Sex |  LicNbr   | State |
| Bob      | Smith   | 19230105 | 123456789 | M   | N4257665  | NJ    |
| Jane     | Doe     | 19670530 | 000000000 | F   | 12457890  | PA    |
-----------------------------------------------------------------------


Пример Отображения:

--------------------------
| Target        | Source |
| first_name    | FName  |
| last_name     | LName  |
| license_state | State  |
| license_id    | LicNbr |
--------------------------


Ожидаемый Результат Выборки:

-------------------------------------------------------
| first_name | last_name | license_state | license_id |
| Bob        | Smith     | NJ            | N4257665   |
| Jane       | Doe       | PA            | 12457890   |
-------------------------------------------------------


Вот несколько данных/таблиц данных, участвующих:
//Datatable to hold the mapping between the source datatable and the target datatable
DataTable dt = new DataTable("dt_map");

//Add columns to the mapping table
DataColumn dc = new DataColumn("source", typeof(string));
dc.DefaultValue = string.Empty;
dt.Columns.Add(dc);

dc = new DataColumn("target", typeof(string));
dc.DefaultValue = string.Empty;
dt.Columns.Add(dc);

//Load initial row data populating the target table column names to map to
dt.BeginLoadData();
dt.Rows.Add("first_name", "");
dt.Rows.Add("last_name", "");
dt.Rows.Add("license_state", "");
dt.Rows.Add("license_id", "");
dt.EndLoadData();

//Add the mapping datatable to the dataset
ds_mstr.Tables.Add(dt.Copy());

//Create the final normalized datatable which be used
dt = new DataTable("dt_final");

//Add columns to the final datatable
dc = new DataColumn("first_name", typeof(string));
dc.DefaultValue = string.Empty;
dt.Columns.Add(dc);

dc = new DataColumn("last_name", typeof(string));
dc.DefaultValue = string.Empty;
dt.Columns.Add(dc);

dc = new DataColumn("license_state", typeof(string));
dc.DefaultValue = string.Empty;
dt.Columns.Add(dc);

dc = new DataColumn("license_id", typeof(string));
dc.DefaultValue = string.Empty;
dt.Columns.Add(dc);

//Add the final datatable to the dataset
ds_mstr.Tables.Add(dt.Copy());


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

Что я уже пробовал:

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

Maciej Los

Ну, а объект "mapping" - это DataTable или словарь?

greenhorn_dev

Это datatable.

2 Ответов

Рейтинг:
0

Beginner Luck

Попробуйте objecttype.Метод getproperty("тип переменной").И getValue(dragRow, нуль)
вы получаете неизвестность
объект TypedragRow
objecttype-это переменная объекта
тип переменной является переменная, чтобы вы


Рейтинг:
0

Maciej Los

Что ж...

Есть несколько способов достичь этого:


  1. использование библиотеки динамических запросов
    Scottgu's Blog-Dynamic LINQ (Часть 1: использование библиотеки динамических запросов LINQ)[^]
  2. использование деревьев выражений
    Как использовать деревья выражений для построения динамических запросов (C# и Visual Basic)[^]
    Динамические запросы LINQ с деревьями выражений-простой разговор[^]
  3. использование делегата Func<t,>
    Делегат Func(T, TResult) (System)[^]

    Пример:
    //source datatable
    DataTable src = new DataTable();
    src.Columns.Add(new DataColumn("FName", typeof(string)));
    src.Columns.Add(new DataColumn("LName", typeof(string)));
    src.Columns.Add(new DataColumn("DOB", typeof(string)));
    src.Columns.Add(new DataColumn("SSN", typeof(string)));
    src.Columns.Add(new DataColumn("Sex", typeof(string)));
    src.Columns.Add(new DataColumn("LicNbr", typeof(string)));
    src.Columns.Add(new DataColumn("State", typeof(string)));
    src.Rows.Add(new object[]{"Bob", "Smith", "19230105", "123456789", "M", "N4257665", "NJ"});
    src.Rows.Add(new object[]{"Jane ", "Doe", "19670530", "000000000", "F", "12457890", "PA"});
    //datatable for map fields
    DataTable map = new DataTable();
    map.Columns.Add(new DataColumn("Target", typeof(string)));
    map.Columns.Add(new DataColumn("Source", typeof(string)));
    map.Rows.Add(new object[]{"first_name","FName"});
    map.Rows.Add(new object[]{"last_name","LName"});
    map.Rows.Add(new object[]{"license_id","LicNbr"});
    map.Rows.Add(new object[]{"license_state","State"});
    //Dictionary object to get KeyValuePair<string, string>
    //this object will be used to "translate" one name of column into second
    Dictionary<string, string> col_map = map.AsEnumerable().ToDictionary(x=>x.Field<string>("Source"), x=>x.Field<string>("Target"));
    //delegate to change the names of columns
    Func<DataRow, Dictionary<string, string>, DataRow> mappedRow = delegate(DataRow row, Dictionary<string, string> mapp)
    		{
    			foreach(DataColumn c in row.Table.Columns)
    			{
    				try {
    					c.ColumnName = mapp[c.ColumnName];
    				} catch (KeyNotFoundException ex)
    				{
    					//ignore error and continue for loop
    					continue;
    				}
    			}
    			return row;
    		};
    
    //query
    var result = src.AsEnumerable()
    		.Select(x=> mappedRow(x, col_map));


    Результат:

    first_name	last_name	DOB			SSN			Sex	license_id	license_state
    Bob 		Smith 		19230105 	123456789 	M 	N4257665 	NJ 
    Jane  		Doe 		19670530 	000000000 	F 	12457890 	PA 


  4. Если вы хотите вернуть только те поля,которые хранятся в словаре, вы должны предоставить способ изменения коллекции столбцов для объекта datatable.


Другие интересные ресурсы:
c# - ссылка на данные по имени столбца в запросе LINQ, а затем фильтрация через сопоставление с другим набором данных-переполнение стека[^]
sql server-сопоставление имен столбцов таблицы sql со свойством объекта, C#, LINQ-переполнение стека[^]


greenhorn_dev

Хотя это приближает меня к цели, в конечном итоге это приводит к переименованию базовой таблицы datatable, а не к сопоставлению таблицы src с таблицей назначения и запросу источника для заполнения места назначения.

Давайте попробуем несколько иной подход, используя словарь и таблицу src как есть. Существует ли запрос LinQ, который будет выбирать из src datatable в том порядке, в котором столбцы хранятся в словаре в качестве ключей, а затем переименовывать выходные результирующие столбцы в значение ключа (а не сам ключ).

ФОК:
-----------------------------------------------------------------------
| Имени | lname в | ДОБ | ССН | секс | LicNbr | Государственной |
/ Боб / Смит| 19230105 | 123456789 | M | N4257665 | NJ |
| Джейн Доу| 19670530 | 000000000 | Ф | 12457890 | ПА |
-----------------------------------------------------------------------

Словарь:
--------------------------
/ Ключ / Значение |
| имени | Имя |
| lname | last_name |
| DOB | |
| SSN | |
| Секс | /
| LicNbr | license_id |
| Государственной | license_state |
--------------------------

назначения объекта DataTable:
-------------------------------------------------------
| first_name | last_name | license_state | license_id |
| Вася | Иванов | N4257665 | Нью-Джерси |
| Джейн / Доу | 12457890 / ПА |
-------------------------------------------------------

Обратите внимание, что порядок столбцов изменился, как и имена столбцов. Я не хочу менять структуру src datatable после того, как она была построена. Я действительно хочу извлечь -- & gt; transform -- & gt; load (ETL) из src --> destination, используя словарь в качестве чертежа для преобразования, если это имеет какой-то смысл.

Maciej Los

Ну, все, что вам нужно сделать, это изменить код в соответствии с вашими потребностями.