Запись данных в файл CSV в C #

198

Я пытаюсь записать в csvфайл строка за строкой, используя язык C #. Вот моя функция

string first = reader[0].ToString();
string second=image.ToString();
string csv = string.Format("{0},{1}\n", first, second);
File.WriteAllText(filePath, csv);

Вся функция выполняется внутри цикла, и каждая строка должна быть записана в csvфайл. В моем случае следующая строка перезаписывает существующую строку, и в конце я получаю единственную запись в CSV-файле, которая является последней. Как я могу написать все строки в csvфайле?

rampuriyaaa
источник
Скорее использовать, StringBuilderа затем сделать один сохранить?
Йохан
1
Если это не та задача, которую вам нужно выполнять ежедневно, я рекомендую использовать LinqPad, который поставляется с удобной функцией для записи данных в csv:Util.WriteCsv (mydatacollection, @"c:\temp\data.csv");
Marco
3
На заметку, убедитесь, что ваши значения CSV закодированы. Т.е., если один из них содержит запятую или символ конца строки, это может испортить ваш файл. Я обычно просто использую сторонний lib для csv.
Маттис Уэссельс
@MatthijsWessels Любые предложения библиотеки?
Араш Мотамеди

Ответы:

313

ОБНОВИТЬ

Еще в наивные времена я предлагал делать это вручную (это было простое решение простого вопроса), однако из-за того, что это становилось все более и более популярным, я бы рекомендовал использовать библиотеку CsvHelper, которая выполняет все проверки безопасности и т. Д.

CSV намного сложнее, чем предполагает вопрос / ответ.

Оригинальный ответ

Поскольку у вас уже есть цикл, попробуйте сделать это следующим образом:

//before your loop
    var csv = new StringBuilder();

//in your loop
    var first = reader[0].ToString();
    var second = image.ToString();
    //Suggestion made by KyleMit
    var newLine = string.Format("{0},{1}", first, second);
    csv.AppendLine(newLine);  

//after your loop
    File.WriteAllText(filePath, csv.ToString());

Или что-то на этот счет. Я рассуждаю так: вам не нужно будет писать в файл для каждого элемента, вы будете открывать поток только один раз, а затем писать в него.

Вы можете заменить

File.WriteAllText(filePath, csv.ToString());

с участием

File.AppendAllText(filePath, csv.ToString());

если вы хотите сохранить предыдущие версии CSV в том же файле

C # 6

Если вы используете c # 6.0, то вы можете сделать следующее

var newLine = $"{first},{second}"

РЕДАКТИРОВАТЬ

Вот ссылка на вопрос, который объясняет, что Environment.NewLineделает.

Johan
источник
1
@ Раджат нет, потому что 2 - это новая строка
Йохан
4
Вы также можете избавиться от {2}и Environment.NewLineиспользовать AppendLineвместоAppend
KyleMit
37
И что происходит, когда ваш CSV-контент содержит запятую, которую нужно экранировать? Вам нужны цитаты. А что происходит, когда нужно убрать цитату? Правильное построение CSV-файлов намного сложнее, чем предполагает этот ответ.
MgSam
1
Я получил "exceptionType": "System.UnauthorizedAccessException",
Чит Кхин
3
Я попробовал ваше решение, но для меня (французская культура) оно работает лучше, используя System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparatorвместо запятой.
А.Писсикат
89

Я очень рекомендую вам пойти по более утомительному маршруту. Особенно, если ваш размер файла большой.

using(var w = new StreamWriter(path))
{
    for( /* your loop */)
    {
        var first = yourFnToGetFirst();
        var second = yourFnToGetSecond();
        var line = string.Format("{0},{1}", first, second);
        w.WriteLine(line);
        w.Flush();
    }
}

File.AppendAllText()открывает новый файл, записывает содержимое и затем закрывает файл. Открытие файлов является гораздо более ресурсоемкой операцией, чем запись данных в открытый поток. Открытие \ закрытие файла внутри цикла приведет к падению производительности.

Подход, предложенный Йоханом, решает эту проблему, сохраняя весь вывод в памяти, а затем записывая его один раз. Однако (в случае больших файлов) ваша программа будет потреблять большое количество оперативной памяти и даже зависать сOutOfMemoryException

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

UPD. Размещено с использованием в нужном месте

Павел Мурыгин
источник
1
Вы должны поместить ваш цикл внутри в usingзаявлении. В противном случае вы будете снова и снова открывать файл снова и снова.
Оливер
2
Хороший ответ. Правильный ответ, если вы удалите «{2}» из строки формата и замените «изображение» на «второй» в той же строке. Кроме того, не забудьте закрыть писатель с помощью w.Close (); w.Flush () не нужен.
movAX13h
6
Этот ответ игнорирует необходимость экранирования символов.
MgSam
Можем ли мы также добавить, что это помогает установить ваш писатель так: новый StreamWriter (путь, ложь, Encoding.UTF8)
Charkins12
если у вас есть несколько сотен миллионов строк ... вы должны очистить каждую строку?
Ардианто Сухендар
31

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

В этом вопросе упоминается несколько вариантов.

Есть ли в C # библиотеки для чтения / записи CSV?

Джепп Андреасен
источник
4
Это единственный разумно правильный ответ здесь. Обидно, что другие плохие ответы, которые пытаются сделать это вручную, имеют так много голосов.
MgSam
17

Я использую решение двух разбора, так как это очень легко поддерживать

// Prepare the values
var allLines = (from trade in proposedTrades
                select new object[] 
                { 
                    trade.TradeType.ToString(), 
                    trade.AccountReference, 
                    trade.SecurityCodeType.ToString(), 
                    trade.SecurityCode, 
                    trade.ClientReference, 
                    trade.TradeCurrency, 
                    trade.AmountDenomination.ToString(), 
                    trade.Amount, 
                    trade.Units, 
                    trade.Percentage, 
                    trade.SettlementCurrency, 
                    trade.FOP, 
                    trade.ClientSettlementAccount, 
                    string.Format("\"{0}\"", trade.Notes),                             
                }).ToList();

// Build the file content
var csv = new StringBuilder();
allLines.ForEach(line => 
{
    csv.AppendLine(string.Join(",", line));            
});

File.WriteAllText(filePath, csv.ToString());
user3495843
источник
1
При таком подходе вы можете столкнуться с нехваткой памяти при создании больших файлов. Если вы удалите .ToList, тогда allLines будет IEnumerbale <object []>. Затем вы можете выбрать вместо этого «для каждого», т.е. allines, Select (line => csv.AppendLine (string.Join (",", line))), что даст вам IEnumerable <string>. Теперь это можно просто передать в файл, но метод WriteAllLines. Теперь это означает, что все это лениво, и вам не нужно вытягивать все в память, но вы все равно получаете синтаксис, которым вы довольны.
RhysC
Я использую ваш метод для записи в CSV-файл. Здорово! Моя последняя реализация требует от меня использования File.AppendAllLines(filePath, lines, Encoding.ASCII);вместо File.WriteAllText(filePath, csv.ToString()); Итак, я делаю что-то очень неуклюжее. После построения csv с использованием StringBuilder я преобразую его в List <string>, чтобы получить IEnumerable для вызова AppendAllLines, который не будет принимать csv.ToString () в качестве параметра: List<string> lines = new List<string>(); lines.Add(csv.ToString(0, csv.Length));у вас есть лучший способ сделать это?
Патриция
12

Вместо того, чтобы звонить каждый раз, AppendAllText()вы можете подумать об открытии файла один раз, а затем написать весь контент один раз:

var file = @"C:\myOutput.csv";

using (var stream = File.CreateText(file))
{
    for (int i = 0; i < reader.Count(); i++)
    {
        string first = reader[i].ToString();
        string second = image.ToString();
        string csvRow = string.Format("{0},{1}", first, second);

        stream.WriteLine(csvRow);
    }
}
Оливер
источник
@aMazing: Извините, опечатка. Сейчас исправлено.
Оливер
Этот ответ включает в себя, что расширение должно быть CSV. Если это xls, все данные будут отображаться в одном столбце ... CSV будет отображаться правильно в каждом столбце.
Гман
11

Просто используйте AppendAllText вместо:

File.AppendAllText(filePath, csv);

Единственным недостатком AppendAllText является то, что он будет выдавать ошибку, когда файл не существует, поэтому это необходимо проверить

Извините, блондин за минуту до прочтения документации . В любом случае, метод WriteAllText перезаписывает все, что было ранее записано в файл, если файл существует.

Обратите внимание, что ваш текущий код не использует правильные новые строки, например, в Блокноте вы увидите все это как одну длинную строку. Измените код на это, чтобы иметь правильные новые строки:

string csv = string.Format("{0},{1}{2}", first, image, Environment.NewLine);
Shadow Wizard - это ухо для тебя
источник
2
@Rajat нет, это запишет символ новой строки сразу после ваших данных изображения.
Shadow Wizard - это ухо для тебя
7

Вместо того, чтобы изобретать велосипед, можно использовать библиотеку. CsvHelperотлично подходит для создания и чтения CSV-файлов. Это операции чтения и записи, основанные на потоке, и, следовательно, также поддерживают операции с большим объемом данных.


Вы можете написать свой CSV, как показано ниже.

using(var textWriter = new StreamWriter(@"C:\mypath\myfile.csv"))
{
    var writer = new CsvWriter(textWriter, CultureInfo.InvariantCulture);
    writer.Configuration.Delimiter = ",";

    foreach (var item in list)
    {
        writer.WriteField( "a" );
        writer.WriteField( 2 );
        writer.WriteField( true );
        writer.NextRecord();
    }
}

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

public class CsvRow
{
    public string Column1 { get; set; }
    public bool Column2 { get; set; }

    public CsvRow(string column1, bool column2)
    {
        Column1 = column1;
        Column2 = column2;
    }
}

IEnumerable<CsvRow> rows = new [] {
    new CsvRow("value1", true),
    new CsvRow("value2", false)
};
using(var textWriter = new StreamWriter(@"C:\mypath\myfile.csv")
{
    var writer = new CsvWriter(textWriter, CultureInfo.InvariantCulture);
    writer.Configuration.Delimiter = ",";
    writer.WriteRecords(rows);
}

value1, правда,

значение2, ложь


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

NtFreX
источник
Возможно, вам придется передать CultureInfo StreamWriter, чтобы это работало.
Алиф
6
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.Configuration;
using System.Data.SqlClient;

public partial class CS : System.Web.UI.Page
{
    protected void ExportCSV(object sender, EventArgs e)
    {
        string constr = ConfigurationManager.ConnectionStrings["constr"].ConnectionString;
        using (SqlConnection con = new SqlConnection(constr))
        {
            using (SqlCommand cmd = new SqlCommand("SELECT * FROM Customers"))
            {
                using (SqlDataAdapter sda = new SqlDataAdapter())
                {
                    cmd.Connection = con;
                    sda.SelectCommand = cmd;
                    using (DataTable dt = new DataTable())
                    {
                        sda.Fill(dt);

                        //Build the CSV file data as a Comma separated string.
                        string csv = string.Empty;

                        foreach (DataColumn column in dt.Columns)
                        {
                            //Add the Header row for CSV file.
                            csv += column.ColumnName + ',';
                        }

                        //Add new line.
                        csv += "\r\n";

                        foreach (DataRow row in dt.Rows)
                        {
                            foreach (DataColumn column in dt.Columns)
                            {
                                //Add the Data rows.
                                csv += row[column.ColumnName].ToString().Replace(",", ";") + ',';
                            }

                            //Add new line.
                            csv += "\r\n";
                        }

                        //Download the CSV file.
                        Response.Clear();
                        Response.Buffer = true;
                        Response.AddHeader("content-disposition", "attachment;filename=SqlExport.csv");
                        Response.Charset = "";
                        Response.ContentType = "application/text";
                        Response.Output.Write(csv);
                        Response.Flush();
                        Response.End();
                    }
                }
            }
        }
    }
}
Алехандро Аро
источник
почему здесь нет строителя строк?
Seabizkit
1
Какой RFC вы использовали, чтобы написать это .Replace(",", ";") + ',' ?
vt100
Этот путь занимает слишком много времени, если у вас более 20000 строк и 30 столбцов
Викрант
3

Обработка запятых

Для обработки запятых внутри значений при использовании string.Format(...), мне помогло следующее:

var newLine = string.Format("\"{0}\",\"{1}\",\"{2}\"",
                              first,
                              second,
                              third                                    
                              );
csv.AppendLine(newLine);

Чтобы объединить это с ответом Йохана, это выглядело бы так:

//before your loop
var csv = new StringBuilder();

//in your loop
  var first = reader[0].ToString();
  var second = image.ToString();
  //Suggestion made by KyleMit
  var newLine = string.Format("\"{0}\",\"{1}\"", first, second);
  csv.AppendLine(newLine);  

//after your loop
File.WriteAllText(filePath, csv.ToString());

Возвращение файла CSV

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

Из хранимой процедуры

public FileContentResults DownloadCSV()
{
  // I have a stored procedure that queries the information I need
  SqlConnection thisConnection = new SqlConnection("Data Source=sv12sql;User ID=UI_Readonly;Password=SuperSecure;Initial Catalog=DB_Name;Integrated Security=false");
  SqlCommand queryCommand = new SqlCommand("spc_GetInfoINeed", thisConnection);
  queryCommand.CommandType = CommandType.StoredProcedure;

  StringBuilder sbRtn = new StringBuilder();

  // If you want headers for your file
  var header = string.Format("\"{0}\",\"{1}\",\"{2}\"",
                             "Name",
                             "Address",
                             "Phone Number"
                            );
  sbRtn.AppendLine(header);

  // Open Database Connection
  thisConnection.Open();
  using (SqlDataReader rdr = queryCommand.ExecuteReader())
  {
    while (rdr.Read())
    {
      // rdr["COLUMN NAME"].ToString();
      var queryResults = string.Format("\"{0}\",\"{1}\",\"{2}\"",
                                        rdr["Name"].ToString(),
                                        rdr["Address"}.ToString(),
                                        rdr["Phone Number"].ToString()
                                       );
      sbRtn.AppendLine(queryResults);
    }
  }
  thisConnection.Close();

  return File(new System.Text.UTF8Encoding().GetBytes(sbRtn.ToString()), "text/csv", "FileName.csv");
}

Из списка

/* To help illustrate */
public static List<Person> list = new List<Person>();

/* To help illustrate */
public class Person
{
  public string name;
  public string address;
  public string phoneNumber;
}

/* The important part */
public FileContentResults DownloadCSV()
{
  StringBuilder sbRtn = new StringBuilder();

  // If you want headers for your file
  var header = string.Format("\"{0}\",\"{1}\",\"{2}\"",
                             "Name",
                             "Address",
                             "Phone Number"
                            );
  sbRtn.AppendLine(header);

  foreach (var item in list)
  {
      var listResults = string.Format("\"{0}\",\"{1}\",\"{2}\"",
                                        item.name,
                                        item.address,
                                        item.phoneNumber
                                       );
      sbRtn.AppendLine(listResults);
    }
  }

  return File(new System.Text.UTF8Encoding().GetBytes(sbRtn.ToString()), "text/csv", "FileName.csv");
}

Надеюсь, это полезно.

Тревор Нестман
источник
2

Это простое руководство по созданию CSV-файлов с использованием C #, которое вы сможете редактировать и расширять в соответствии со своими потребностями.

Сначала вам нужно создать новое консольное приложение Visual Studio C #, для этого необходимо выполнить следующие шаги.

Пример кода создаст CSV-файл с именем MyTest.csv в указанном вами месте. Содержимое файла должно состоять из 3 именованных столбцов с текстом в первых 3 строках.

https://tidbytez.com/2018/02/06/how-to-create-a-csv-file-with-c/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace CreateCsv
{
    class Program
    {
        static void Main()
        {
            // Set the path and filename variable "path", filename being MyTest.csv in this example.
            // Change SomeGuy for your username.
            string path = @"C:\Users\SomeGuy\Desktop\MyTest.csv";

            // Set the variable "delimiter" to ", ".
            string delimiter = ", ";

            // This text is added only once to the file.
            if (!File.Exists(path))
            {
                // Create a file to write to.
                string createText = "Column 1 Name" + delimiter + "Column 2 Name" + delimiter + "Column 3 Name" + delimiter + Environment.NewLine;
                File.WriteAllText(path, createText);
            }

            // This text is always added, making the file longer over time
            // if it is not deleted.
            string appendText = "This is text for Column 1" + delimiter + "This is text for Column 2" + delimiter + "This is text for Column 3" + delimiter + Environment.NewLine;
            File.AppendAllText(path, appendText);

            // Open the file to read from.
            string readText = File.ReadAllText(path);
            Console.WriteLine(readText);
        }
    }
}
Bloggins
источник
2
Ссылка на решение приветствуется, но, пожалуйста, убедитесь, что ваш ответ полезен без нее: добавьте контекст вокруг ссылки, чтобы ваши коллеги-пользователи имели представление о том, что это такое и почему, а затем процитируйте наиболее релевантную часть страницы, которую вы ' Повторная ссылка на случай, если целевая страница недоступна. Ответы, которые немного больше, чем ссылка, могут быть удалены.
Шри
Ах я вижу. Спасибо за ответ. Я сделал изменения выделены. Пожалуйста, до голосования.
Bloggins
0

Возможно, вам просто нужно добавить перевод строки "\n\r".

OneWileyDog
источник
0

Вот еще одна библиотека с открытым исходным кодом для создания файла CSV легко, Cinchoo ETL

List<dynamic> objs = new List<dynamic>();

dynamic rec1 = new ExpandoObject();
rec1.Id = 10;
rec1.Name = @"Mark";
rec1.JoinedDate = new DateTime(2001, 2, 2);
rec1.IsActive = true;
rec1.Salary = new ChoCurrency(100000);
objs.Add(rec1);

dynamic rec2 = new ExpandoObject();
rec2.Id = 200;
rec2.Name = "Tom";
rec2.JoinedDate = new DateTime(1990, 10, 23);
rec2.IsActive = false;
rec2.Salary = new ChoCurrency(150000);
objs.Add(rec2);

using (var parser = new ChoCSVWriter("emp.csv").WithFirstLineHeader())
{
    parser.Write(objs);
}

Для получения дополнительной информации, пожалуйста, прочитайте статью об использовании CodeProject .

Райн
источник
0

Один простой способ избавиться от проблемы с перезаписью - File.AppendTextдобавить строку в конце файла как

void Main()
{
    using (System.IO.StreamWriter sw = System.IO.File.AppendText("file.txt"))
    {          
        string first = reader[0].ToString();
        string second=image.ToString();
        string csv = string.Format("{0},{1}\n", first, second);
        sw.WriteLine(csv);
    }
} 
Винод Шривастав
источник
0
enter code here

string string_value = string.Empty;

        for (int i = 0; i < ur_grid.Rows.Count; i++)
        {
            for (int j = 0; j < ur_grid.Rows[i].Cells.Count; j++)
            {
                if (!string.IsNullOrEmpty(ur_grid.Rows[i].Cells[j].Text.ToString()))
                {
                    if (j > 0)
                        string_value= string_value+ "," + ur_grid.Rows[i].Cells[j].Text.ToString();
                    else
                    {
                        if (string.IsNullOrEmpty(string_value))
                            string_value= ur_grid.Rows[i].Cells[j].Text.ToString();
                        else
                            string_value= string_value+ Environment.NewLine + ur_grid.Rows[i].Cells[j].Text.ToString();
                    }
                }
            }
        }


        string where_to_save_file = @"d:\location\Files\sample.csv";
        File.WriteAllText(where_to_save_file, string_value);

        string server_path = "/site/Files/sample.csv";
        Response.ContentType = ContentType;
        Response.AppendHeader("Content-Disposition", "attachment; filename=" + Path.GetFileName(server_path));
        Response.WriteFile(server_path);
        Response.End();
демон
источник
0
public static class Extensions
{
    public static void WriteCSVLine(this StreamWriter writer, IEnumerable<string> fields)
    {
        const string q = @"""";
        writer.WriteLine(string.Join(",",
            fields.Select(
                v => (v.Contains(',') || v.Contains('"') || v.Contains('\n') || v.Contains('\r')) ? $"{q}{v.Replace(q, q + q)}{q}" : v
                )));
    }

    public static void WriteFields(this StreamWriter writer, params string[] fields) => WriteFields(writer, (IEnumerable<string>)fields);
}

Это должно позволить вам написать файл CSV довольно просто. Использование:

StreamWriter writer = new StreamWriter("myfile.csv");
writer.WriteCSVLine(new[]{"A", "B"});
trinalbadger587
источник