С# Чтение CSV в DataTable и вызов строк/столбцов

В настоящее время я работаю над небольшим проектом, и я застрял с проблемой, которую в настоящее время не могу решить...

У меня есть несколько файлов «.CSV», которые я хочу прочитать, все они имеют одни и те же данные только с разными значениями.

Header1;Value1;Info1
Header2;Value2;Info2
Header3;Value3;Info3

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

Затем ему нужно прочитать значение 1 - значение 3 (они перечислены во 2-м столбце), и, кроме того, мне нужно создать еще один заголовок -> заголовок4 с данными «Info2», которые всегда помещаются в столбец 3 и строку 2 (другие значения столбца 3 я могу игнорировать).

Таким образом, результат после первого файла должен выглядеть так:

Header1;Header2;Header3;Header4;
Value1;Value2;Value3;Info2;

И после нескольких файлов это должно быть так:

Header1;Header2;Header3;Header4;
Value1;Value2;Value3;Value4;
Value1b;Value2b;Value3b;Value4b;
Value1c;Value2c;Value3c;Value4c;

Я попробовал это с OleDB, но я получаю сообщение об ошибке «отсутствует ISAM», которое я не могу исправить. Код, который я использовал, следующий:

public DataTable ReadCsv(string fileName)
    {
        DataTable dt = new DataTable("Data");
       /* using (OleDbConnection cn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\"" + 
            Path.GetDirectoryName(fileName) + "\";Extendet Properties ='text;HDR=yes;FMT=Delimited(,)';"))
        */
        using (OleDbConnection cn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
            Path.GetDirectoryName(fileName) + ";Extendet Properties ='text;HDR=yes;FMT=Delimited(,)';"))
        {
            using(OleDbCommand cmd = new OleDbCommand(string.Format("select *from [{0}]", new FileInfo(fileName).Name,cn)))
            {
                cn.Open();
                using(OleDbDataAdapter adapter = new OleDbDataAdapter(cmd))
                {
                    adapter.Fill(dt);
                }
            }
        }


        return dt;
    }

Еще одна попытка, которую я сделал, заключалась в использовании StreamReader. Но заголовки находятся не в том месте, и я не знаю, как это изменить + сделать это для каждого файла. Код, который я пробовал, следующий:

  public static DataTable ReadCsvFilee(string path)
    {  

        DataTable oDataTable = new DataTable();
        var fileNames = Directory.GetFiles(path);
        foreach (var fileName in fileNames)

        {

            //initialising a StreamReader type variable and will pass the file location
            StreamReader oStreamReader = new StreamReader(fileName);

            // CONTROLS WHETHER WE SKIP A ROW OR NOT
            int RowCount = 0;
            // CONTROLS WHETHER WE CREATE COLUMNS OR NOT
            bool hasColumns = false;
            string[] ColumnNames = null;
            string[] oStreamDataValues = null;
            //using while loop read the stream data till end
            while (!oStreamReader.EndOfStream)
            { 

                String oStreamRowData = oStreamReader.ReadLine().Trim();
                if (oStreamRowData.Length > 0)
                { 

                    oStreamDataValues = oStreamRowData.Split(';');
                    //Bcoz the first row contains column names, we will poluate 
                    //the column name by
                    //reading the first row and RowCount-0 will be true only once
                    // CHANGE TO CHECK FOR COLUMNS CREATED                      
                    if (!hasColumns)
                    {
                        ColumnNames = oStreamRowData.Split(';');

                        //using foreach looping through all the column names
                        foreach (string csvcolumn in ColumnNames)
                        {
                            DataColumn oDataColumn = new DataColumn(csvcolumn.ToUpper(), typeof(string));

                            //setting the default value of empty.string to newly created column
                            oDataColumn.DefaultValue = string.Empty;

                            //adding the newly created column to the table
                            oDataTable.Columns.Add(oDataColumn);
                        }
                        // SET COLUMNS CREATED
                        hasColumns = true;
                        // SET RowCount TO 0 SO WE KNOW TO SKIP COLUMNS LINE
                        RowCount = 0;
                    }
                    else
                    {
                        // IF RowCount IS 0 THEN SKIP COLUMN LINE
                        if (RowCount++ == 0) continue;
                        //creates a new DataRow with the same schema as of the oDataTable            
                        DataRow oDataRow = oDataTable.NewRow();

                        //using foreach looping through all the column names
                        for (int i = 0; i < ColumnNames.Length; i++)
                        {
                            oDataRow[ColumnNames[i]] = oStreamDataValues[i] == null ? string.Empty : oStreamDataValues[i].ToString();
                        }

                        //adding the newly created row with data to the oDataTable       
                        oDataTable.Rows.Add(oDataRow);
                    }

                }
            }
            //close the oStreamReader object
            oStreamReader.Close();
            //release all the resources used by the oStreamReader object
            oStreamReader.Dispose();
        }
            return oDataTable;
        }

Я благодарен всем, кто готов помочь. И спасибо, что дочитали до этого места!

Искренне Ваш


person christian890    schedule 23.03.2018    source источник
comment
Вам не нужен драйвер для чтения файлов CSV, в самой простой форме это просто текстовые файлы с разделителями. Вы можете читать по одной строке за раз и разделять ее. То, что вы описываете, не является файлом CSV. Как кто-то мог догадаться, что Value4 предназначено для другого поля?   -  person Panagiotis Kanavos    schedule 23.03.2018
comment
Если бы заголовки и значения не были перепутаны, вы могли бы прочитать файл, как если бы он содержал 3 поля, сгруппировать по полю first/header и использовать это значение ключа в качестве имени поля, значения группы как Предметы. Пожалуйста, объясните логику значений Header4:Value4 и игнорирования. Возможно, это формат, который просто выглядит как CSV?   -  person Panagiotis Kanavos    schedule 23.03.2018
comment
О, это моя беда!! Значение 4 не похоже на значение 1-3, это просто еще одна информация, которую я хочу прочитать. Извините за это, я должен был назвать это, например, Info1. Мне не нужна никакая информация из столбца 3, только это одно значение информации4, о других я не возражаю. Позвольте мне исправить это в моем вопросе. Итак, это просто еще одно значение без заголовка в самом CSV. Поэтому мне нужно создать для него новый заголовок в моей таблице данных с именем Header4.   -  person christian890    schedule 23.03.2018


Ответы (3)


(Добавление в качестве еще одного ответа, чтобы сделать его незагроможденным)

void ProcessMyFiles(string folderName)
{
    List<MyData> d = new List<MyData>();
    var files = Directory.GetFiles(folderName);
    foreach (var file in files)
    {
        OpenAndParse(file, d);
    }

    string[] headers = GetHeaders(files[0]);
    DataGridView dgv = new DataGridView {Dock=DockStyle.Fill};
    dgv.DataSource = d;
    dgv.ColumnAdded += (sender, e) => {e.Column.HeaderText = headers[e.Column.Index];};

    Form f = new Form();
    f.Controls.Add(dgv);
    f.Show();
}

string[] GetHeaders(string filename)
{
    var lines = File.ReadAllLines(filename);
    var parsed = lines.Select(l => l.Split(';')).ToArray();
    return new string[] { parsed[0][0], parsed[1][0], parsed[2][0], parsed[1][0] };
}

void OpenAndParse(string filename, List<MyData> d)
{
    var lines = File.ReadAllLines(filename);
    var parsed = lines.Select(l => l.Split(';')).ToArray();
    var data = new MyData
    {
        Col1 = parsed[0][1],
        Col2 = parsed[1][1],
        Col3 = parsed[2][1],
        Col4 = parsed[1][2]
    };
    d.Add(data);
}

public class MyData
{
    public string Col1 { get; set; }
    public string Col2 { get; set; }
    public string Col3 { get; set; }
    public string Col4 { get; set; }
}
person Cetin Basoz    schedule 23.03.2018
comment
Я действительно НЕ ожидал такой большой помощи! Большое спасибо! ЭТО абсолютно решило мою проблему! Хороших выходных! Большое спасибо - person christian890; 23.03.2018

Если я вас правильно понял, там строгий парсинг такой:

string OpenAndParse(string filename, bool firstFile=false)
{
    var lines = File.ReadAllLines(filename);

    var parsed = lines.Select(l => l.Split(';')).ToArray();

    var header = $"{parsed[0][0]};{parsed[1][0]};{parsed[2][0]};{parsed[1][0]}\n";
    var data   = $"{parsed[0][1]};{parsed[1][1]};{parsed[2][1]};{parsed[1][2]}\n";

    return firstFile
    ? $"{header}{data}"
    : $"{data}";
}

Куда он вернется - если первый файл:

Header1;Header2;Header3;Header2
Value1;Value2;Value3;Value4

если не первый файл:

Value1;Value2;Value3;Value4

Если я прав, отдых заключается в том, чтобы запустить это со списком файлов и объединить результаты в выходной файл.

РЕДАКТИРОВАТЬ: против каталога:

void ProcessFiles(string folderName, string outputFileName)
{
    bool firstFile = true;
    foreach (var f in Directory.GetFiles(folderName))
    {
        File.AppendAllText(outputFileName, OpenAndParse(f, firstFile));
        firstFile = false;
    }
}

Примечание. Я пропустил, что вам нужен DataTable, а не выходной файл. Затем вы можете просто создать список и поместить результаты в этот список, сделав список источником данных для ваших данных (тогда зачем вам использовать там точки с запятой? Вероятно, все, что вам нужно, это просто прикрепить значения массива к списку).

person Cetin Basoz    schedule 23.03.2018
comment
Благодарю вас! Предположение, которое вы сделали, абсолютно верно! И я думаю, что это может сработать. В настоящее время у меня проблема с его тестированием (извините, я немного новичок в кодировании). В вашем коде вы добавляете текст в файл. Есть ли способ добавить его в DataTable? А за что $? в моем коде говорится, что этот знак был неожиданным, а другие ошибки, которые я получаю, - это заголовок/данные, которые никогда не используются - person christian890; 23.03.2018
comment
Как я уже отмечал в своей заметке, я очень скучал по вам, когда вы хотели получить данные. Список может быть источником данных для DataTable. Я постараюсь собрать для вас пример. - person Cetin Basoz; 23.03.2018
comment
О, я этого не читал! Спасибо, я очень ценю эту помощь! знак равно - person christian890; 23.03.2018

Я не знаю, лучший ли это способ сделать это. Но что бы я сделал в вашем случае, так это переписать CSV обычным способом, читая все файлы, а затем создать поток, содержащий новый созданный CSV.

Это будет выглядеть примерно так:

     var csv = new StringBuilder();
            csv.AppendLine("Header1;Header2;Header3;Header4");
            foreach (var item in file)
            {
                var newLine = string.Format("{0},{1},{2},{3}", item.value1, item.value2, item.value3, item.value4);
                csv.AppendLine(newLine);
            }

            //Create Stream
            MemoryStream stream = new MemoryStream();
            StreamReader reader = new StreamReader(stream);

            //Fill your data table here with your values

Надеюсь, это поможет.

person Zakaria Sahmane    schedule 23.03.2018
comment
Спасибо за ваше время! Я тоже об этом думал, но мне приходится читать до тысячи таких файлов и всегда переписывать это не лучший вариант / замедляет процесс, не так ли? Mhhh, нет ли способа просто вызвать столбец 2 и вызвать его (вместо строк разделить его на столбцы), а затем прочитать только столбец 3, строку 2 и поместить его в заголовок 4? - person christian890; 23.03.2018