Zapisywanie danych do pliku CSV w C #

198

Próbuję pisać do csvpliku wiersz po rzędzie przy użyciu języka C #. Oto moja funkcja

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

Cała funkcja działa w pętli, a każdy wiersz powinien być zapisany w csvpliku. W moim przypadku następny wiersz zastępuje istniejący wiersz, a na końcu otrzymuję tylko jeden rekord w pliku csv, który jest ostatnim. Jak mogę zapisać wszystkie wiersze w csvpliku?

rampuriyaaa
źródło
Zamiast tego użyj a, StringBuildera następnie zapisz jeden?
Johan
1
Jeśli nie jest to zadanie, które musisz codziennie wykonywać, polecam użycie LinqPad, który ma przydatną funkcję do zapisywania danych w pliku csv:Util.WriteCsv (mydatacollection, @"c:\temp\data.csv");
Marco,
3
Na marginesie, upewnij się, że twoje wartości csv są zakodowane. To znaczy, jeśli jeden z nich zawiera przecinek lub znak końca wiersza, może to popsuć plik. Zwykle używam biblioteki innej firmy do obsługi plików CSV.
Matthijs Wessels
@MatthijsWessels Wszelkie sugestie dotyczące biblioteki?
Arash Motamedi,

Odpowiedzi:

312

AKTUALIZACJA

W dawnych naiwnych czasach zasugerowałem zrobienie tego ręcznie (było to proste rozwiązanie prostego pytania), jednak ze względu na to, że staje się to coraz bardziej popularne, polecam korzystanie z biblioteki CsvHelper, która wykonuje wszystkie kontrole bezpieczeństwa itp.

CSV jest o wiele bardziej skomplikowane niż sugeruje pytanie / odpowiedź.

Oryginalna odpowiedź

Ponieważ masz już pętlę, rozważ zrobienie tego w ten sposób:

//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());

Lub coś w tym celu. Moje rozumowanie brzmi: nie będziesz musiał pisać do pliku dla każdego elementu, otworzysz strumień tylko raz, a następnie napiszesz do niego.

Możesz wymienić

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

z

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

jeśli chcesz zachować poprzednie wersje csv w tym samym pliku

C # 6

Jeśli używasz c # 6.0, możesz wykonać następujące czynności

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

EDYTOWAĆ

Oto link do pytania, które wyjaśnia, co Environment.NewLinerobi.

Johan
źródło
1
@Rajat nie, ponieważ 2 to nowa linia
Johan,
4
Można też pozbyć {2}i Environment.NewLinei używać AppendLinezamiastAppend
KyleMit
37
A co się stanie, gdy w treści CSV pojawi się przecinek, który wymaga ucieczki? Potrzebujesz cytatów. A co się stanie, gdy cytat będzie wymagał ucieczki? Prawidłowe budowanie plików CSV jest znacznie bardziej skomplikowane, niż sugeruje to odpowiedź.
MgSam,
1
Mam „
wyjątekTyp
3
Wypróbowałem twoje rozwiązanie, ale dla mnie (kultura francuska) działa lepiej, używając System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparatorzamiast przecinka.
A.Pissicat,
89

Gorąco polecam wybranie bardziej nudnej trasy. Zwłaszcza jeśli twój plik jest duży.

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()otwiera nowy plik, zapisuje zawartość, a następnie zamyka plik. Otwieranie plików jest operacją znacznie obciążającą zasoby, niż zapisywanie danych w otwartym strumieniu. Otwarcie \ zamknięcie pliku w pętli spowoduje spadek wydajności.

Podejście sugerowane przez Johna rozwiązuje ten problem, przechowując wszystkie dane wyjściowe w pamięci, a następnie zapisując je jeden raz. Jednak (w przypadku dużych plików) program zużywa dużo pamięci RAM, a nawet ulega awariiOutOfMemoryException

Kolejną zaletą mojego rozwiązania jest to, że możesz zaimplementować pauzę \ wznawianie poprzez zapisanie bieżącej pozycji w danych wejściowych.

aktualiz. Umieszczono za pomocą we właściwym miejscu

Pavel Murygin
źródło
1
Należy umieścić swoje pętli wewnętrznej w usingoświadczeniu. W przeciwnym razie plik będzie cały czas otwierany ponownie.
Oliver
2
Dobra odpowiedź. Prawidłowa odpowiedź, jeśli usuniesz „{2}” z ciągu formatu i zastąpisz „image” ciągiem „second” w tym samym wierszu. Nie zapomnij także zamknąć programu piszącego za pomocą w.Close (); w.Flush () nie jest potrzebne.
movAX13h
6
Ta odpowiedź ignoruje potrzebę ucieczki znaków.
MgSam
Czy możemy również dodać, że pomaga to ustawić twojego pisarza w ten sposób: nowy StreamWriter (ścieżka, fałsz, Encoding.UTF8)
Charkins12
jeśli masz kilkaset milionów linii ... czy powinieneś spłukać każdą linię?
Ardianto Suhendar
31

Ręczne pisanie plików csv może być trudne, ponieważ dane mogą zawierać przecinki i znaki nowej linii. Sugeruję użycie zamiast tego istniejącej biblioteki.

To pytanie wymienia kilka opcji.

Czy w C # są jakieś biblioteki czytników / programów piszących CSV?

Jeppe Andreasen
źródło
4
To jedyna uzasadniona odpowiedź tutaj. Szkoda, że ​​inne złe odpowiedzi, które próbują to zrobić ręcznie, mają tak wiele pozytywnych opinii.
MgSam
17

Używam rozwiązania dwóch analiz, ponieważ jest ono bardzo łatwe w utrzymaniu

// 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());
użytkownik3495843
źródło
1
Podczas tworzenia dużych plików może się pojawić presja pamięci. Jeśli usunąłeś .ToList, wówczas wszystkie linie byłyby IEnumerbale <obiekt []>. Następnie możesz wybrać tę opcję zamiast „dla każdego”, tj. Alliny, Wybierz (linia => csv.AppendLine (string.Join („,”, linia))), co da ci IEnumerable <ciąg>. Teraz można to po prostu przekazać do pliku, ale metodę WriteAllLines. Oznacza to teraz, że cała sprawa jest leniwa i nie musisz wciągać wszystkiego do pamięci, ale nadal otrzymujesz składnię, z której jesteś zadowolony.
RhysC
Używam twojej metody do zapisu do pliku csv. Wspaniale! Moja najnowsza realizacja wymaga mnie użyć File.AppendAllLines(filePath, lines, Encoding.ASCII);zamiast File.WriteAllText(filePath, csv.ToString()); Więc robię coś bardzo przylegający. Po zbudowaniu pliku csv za pomocą StringBuilder przekształcam go w List <string>, aby uzyskać IEnumerable dla wywołania AppendAllLines, które nie zaakceptuje csv.ToString () jako parametru: List<string> lines = new List<string>(); lines.Add(csv.ToString(0, csv.Length));czy masz lepszy sposób to zrobić?
Patricia
12

Zamiast dzwonić za każdym razem, AppendAllText()możesz pomyśleć o otwarciu pliku raz, a następnie napisać całą zawartość raz:

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);
    }
}
Oliver
źródło
@aMazing: Przepraszamy, literówka. Jest teraz naprawiony.
Oliver,
Ta odpowiedź obejmuje, że rozszerzenie musi mieć format csv. Jeśli jest to xls, wszystkie dane pojawią się w jednej kolumnie ... csv będą poprawnie wyświetlane w każdej kolumnie.
gman
10

Wystarczy użyć AppendAllText zamiast:

File.AppendAllText(filePath, csv);

Jedynym minusem AppendAllText jest to, że zgłasza błąd, gdy plik nie istnieje, więc należy to sprawdzić

Przepraszam, blond chwilę przed przeczytaniem dokumentacji . W każdym razie metoda WriteAllText zastępuje wszystko, co zostało wcześniej zapisane w pliku, jeśli plik istnieje.

Zauważ, że twój obecny kod nie używa właściwych nowych linii, na przykład w Notatniku zobaczysz to wszystko jako jedną długą linię. Zmień kod na ten, aby mieć właściwe nowe wiersze:

string csv = string.Format("{0},{1}{2}", first, image, Environment.NewLine);
Shadow Wizard is Ear For You
źródło
2
@Rajat nie, to zapisze nowy znak linii zaraz po danych obrazu.
Shadow Wizard is Ear For You
7

Zamiast wynaleźć koło na nowo, można użyć biblioteki. CsvHelperdoskonale nadaje się do tworzenia i czytania plików csv. Operacje odczytu i zapisu są oparte na strumieniu, dlatego też obsługują operacje z dużą ilością danych.


Możesz napisać csv w następujący sposób.

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();
    }
}

Ponieważ biblioteka używa odbicia, pobierze dowolny typ i parsuje go bezpośrednio.

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);
}

wartość1, prawda

wartość2, fałsz


Jeśli chcesz dowiedzieć się więcej o konfiguracjach bibliotek i możliwościach, możesz to zrobić tutaj .

NtFreX
źródło
Aby to zadziałało, może być konieczne przekazanie CultureInfo do StreamWriter.
alif
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();
                    }
                }
            }
        }
    }
}
Alejandro Haro
źródło
dlaczego nie ma tutaj kreatora ciągów?
Seabizkit
1
Które RFC użyłeś do napisania tego .Replace(",", ";") + ',' :?
vt100
Ta droga trwa zbyt długo, jeśli masz ponad 20000 wierszy i 30 kolumn
Vikrant
3

Obsługa przecinków

Do obsługi przecinków wewnątrz wartości podczas używania string.Format(...), działało dla mnie:

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

Aby połączyć to z odpowiedzią Johana, wyglądałoby to tak:

//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());

Zwracanie pliku CSV

Jeśli po prostu chcesz zwrócić plik zamiast zapisywać go w lokalizacji, jest to przykład tego, jak to zrobiłem:

Z procedury przechowywanej

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");
}

Z listy

/* 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");
}

Mam nadzieję, że jest to pomocne.

Trevor Nestman
źródło
2

Jest to prosty samouczek na temat tworzenia plików csv za pomocą C #, które będziesz mógł edytować i rozwijać w celu dostosowania do własnych potrzeb.

Najpierw musisz utworzyć nową aplikację konsolową Visual Studio C #. Aby to zrobić, należy wykonać następujące czynności.

Przykładowy kod utworzy plik csv o nazwie MyTest.csv w podanej lokalizacji. Zawartość pliku powinna mieć 3 nazwane kolumny z tekstem w pierwszych 3 wierszach.

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
źródło
2
Link do rozwiązania jest mile widziany, ale upewnij się, że Twoja odpowiedź jest przydatna bez niego: dodaj kontekst wokół linku, aby inni użytkownicy mieli pojęcie, co to jest i dlaczego tam jest, a następnie zacytuj najbardziej odpowiednią część strony, którą „ ponowne linkowanie na wypadek, gdyby strona docelowa była niedostępna. Odpowiedzi, które są niewiele więcej niż link, mogą zostać usunięte.
Shree
O, rozumiem. Dziękujemy za opinię. Podkreśliłem zmiany. Proszę głosować.
Bloggins,
0

Konieczne może być dodanie nowego wiersza "\n\r".

OneWileyDog
źródło
0

Oto kolejna biblioteka open source do łatwego tworzenia pliku 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);
}

Aby uzyskać więcej informacji, przeczytaj artykuł CodeProject na temat użytkowania.

RajN
źródło
0

Jednym prostym sposobem na pozbycie się problemu z nadpisywaniem jest File.AppendTextdodanie linii na końcu pliku jako

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);
    }
} 
Vinod Srivastav
źródło
0
enter code here

ciąg 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();
demon
źródło
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);
}

To powinno pozwolić ci napisać plik csv po prostu. Stosowanie:

StreamWriter writer = new StreamWriter("myfile.csv");
writer.WriteCSVLine(new[]{"A", "B"});
trinalbadger587
źródło