c#csvへのデータテーブル


113

次のコードが機能しない理由を誰かに教えてもらえますか?データはcsvファイルに保存されますが、データは分離されません。すべては各行の最初のセル内に存在します。

StringBuilder sb = new StringBuilder();

foreach (DataColumn col in dt.Columns)
{
    sb.Append(col.ColumnName + ',');
}

sb.Remove(sb.Length - 1, 1);
sb.Append(Environment.NewLine);

foreach (DataRow row in dt.Rows)
{
    for (int i = 0; i < dt.Columns.Count; i++)
    {
        sb.Append(row[i].ToString() + ",");
    }

    sb.Append(Environment.NewLine);
}

File.WriteAllText("test.csv", sb.ToString());

ありがとう。



高性能エクステンションを開発しました。この回答を
Nigje

回答:


229

次の短いバージョンはExcelで正常に開きますが、おそらく問題は末尾のコンマでした

.net = 3.5

StringBuilder sb = new StringBuilder(); 

string[] columnNames = dt.Columns.Cast<DataColumn>().
                                  Select(column => column.ColumnName).
                                  ToArray();
sb.AppendLine(string.Join(",", columnNames));

foreach (DataRow row in dt.Rows)
{
    string[] fields = row.ItemArray.Select(field => field.ToString()).
                                    ToArray();
    sb.AppendLine(string.Join(",", fields));
}

File.WriteAllText("test.csv", sb.ToString());

.net> = 4.0

Timが指摘したように、.net> = 4を使用している場合は、さらに短くすることができます。

StringBuilder sb = new StringBuilder(); 

IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                  Select(column => column.ColumnName);
sb.AppendLine(string.Join(",", columnNames));

foreach (DataRow row in dt.Rows)
{
    IEnumerable<string> fields = row.ItemArray.Select(field => field.ToString());
    sb.AppendLine(string.Join(",", fields));
}

File.WriteAllText("test.csv", sb.ToString());

クリスチャンが提案したように、フィールドでエスケープする特殊文字を処理する場合は、ループブロックを次のように置き換えます。

foreach (DataRow row in dt.Rows)
{
    IEnumerable<string> fields = row.ItemArray.Select(field => 
      string.Concat("\"", field.ToString().Replace("\"", "\"\""), "\""));
    sb.AppendLine(string.Join(",", fields));
}

そして最後の提案として、大きなドキュメントがメモリに残るのを避けるために、ドキュメント全体ではなく、行ごとにcsvコンテンツを書くことができます。


2
ItemArray新しいにコピーする必要はありませんString[]。.NET4 .ToArray()で省略して、(編集済み)をString.Join受け取るオーバーロードを使用できますIEnumerable<T>
Tim Schmelter

2
@TimSchmelter、はい、ただしこれらのオーバーロードは.net4で導入されました。OPが.net <4を使用している場合、コードはコンパイルされません
vc 74

18
このメソッドは、列値内のコンマを考慮しません。
クリスチャン

2
代わりにIEnumerable <string> fields = row.ItemArray.Select(field => field.ToString()。Replace( "\" "、" \ "\" ")); sb.AppendLine(" \ "" + string.Join ( "\"、\ ""、fields)+ "\" ");
クリスチャン

2
@ Si8どういう意味ですか?この回答はdbコンポーネントのみを使用しており&nbsp、HTML / XMLドキュメントの典型です。これは、表が含まれていない限り、それを生成し、上記のコードではありません&nbsp;明示的に
VC 74

36

これを拡張クラスにラップしました。これにより、以下を呼び出すことができます。

myDataTable.WriteToCsvFile("C:\\MyDataTable.csv");

任意のDataTable。

public static class DataTableExtensions 
{
    public static void WriteToCsvFile(this DataTable dataTable, string filePath) 
    {
        StringBuilder fileContent = new StringBuilder();

        foreach (var col in dataTable.Columns) 
        {
            fileContent.Append(col.ToString() + ",");
        }

        fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);

        foreach (DataRow dr in dataTable.Rows) 
        {
            foreach (var column in dr.ItemArray) 
            {
                fileContent.Append("\"" + column.ToString() + "\",");
            }

            fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);
        }

        System.IO.File.WriteAllText(filePath, fileContent.ToString());
    }
}

24

Paul Grimshawの回答に基づく新しい拡張関数。私はそれをクリーンアップし、予期しないデータを処理する機能を追加しました。(空のデータ、埋め込まれた引用、および見出しのカンマ...)

また、より柔軟な文字列を返します。テーブルオブジェクトに構造が含まれていない場合は、Nullを返します。

    public static string ToCsv(this DataTable dataTable) {
        StringBuilder sbData = new StringBuilder();

        // Only return Null if there is no structure.
        if (dataTable.Columns.Count == 0)
            return null;

        foreach (var col in dataTable.Columns) {
            if (col == null)
                sbData.Append(",");
            else
                sbData.Append("\"" + col.ToString().Replace("\"", "\"\"") + "\",");
        }

        sbData.Replace(",", System.Environment.NewLine, sbData.Length - 1, 1);

        foreach (DataRow dr in dataTable.Rows) {
            foreach (var column in dr.ItemArray) {
                if (column == null)
                    sbData.Append(",");
                else
                    sbData.Append("\"" + column.ToString().Replace("\"", "\"\"") + "\",");
            }
            sbData.Replace(",", System.Environment.NewLine, sbData.Length - 1, 1);
        }

        return sbData.ToString();
    }

次のように呼び出します。

var csvData = dataTableOject.ToCsv();

1
これはここの残りの中で最高です。よくやった。ありがとう
Fandango68

素晴らしいソリューション。ローカルでコメントを追加しましたが、山に登らなくてもすぐに使用できました。ありがとうございました。
j.hull

これが気に入りました!これを非静的メソッドとして使用し、DataTableをパラメーターとして渡しました。うまくいきました、ありがとう。
キッドコーダー

9

呼び出しコードがSystem.Windows.Formsアセンブリを参照している場合は、根本的に異なるアプローチを検討できます。私の戦略は、フレームワークによってすでに提供されている関数を使用して、数行のコードでこれを達成し、列と行をループする必要がないことです。以下のコードが行うことは、プログラムでDataGridViewオンザフライを作成し、をに設定するDataGridView.DataSourceことDataTableです。次に、プログラムですべてのセル(ヘッダーを含む)を選択してDataGridViewを呼び出しDataGridView.GetClipboardContent()、結果をWindowsに配置しますClipboard。次に、クリップボードの内容をへの呼び出しにFile.WriteAllText()「貼り付け」、「貼り付け」のフォーマットをとして指定していることを確認しますTextDataFormat.CommaSeparatedValue

これがコードです:

public static void DataTableToCSV(DataTable Table, string Filename)
{
    using(DataGridView dataGrid = new DataGridView())
    {
        // Save the current state of the clipboard so we can restore it after we are done
        IDataObject objectSave = Clipboard.GetDataObject();

        // Set the DataSource
        dataGrid.DataSource = Table;
        // Choose whether to write header. Use EnableWithoutHeaderText instead to omit header.
        dataGrid.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;
        // Select all the cells
        dataGrid.SelectAll();
        // Copy (set clipboard)
        Clipboard.SetDataObject(dataGrid.GetClipboardContent());
        // Paste (get the clipboard and serialize it to a file)
        File.WriteAllText(Filename,Clipboard.GetText(TextDataFormat.CommaSeparatedValue));              

        // Restore the current state of the clipboard so the effect is seamless
        if(objectSave != null) // If we try to set the Clipboard to an object that is null, it will throw...
        {
            Clipboard.SetDataObject(objectSave);
        }
    }
}

また、開始する前にクリップボードの内容を保存し、完了したら復元することを確認します。これにより、ユーザーが次に貼り付けようとしたときに予期しないガベージが大量に発生することがなくなります。このアプローチの主な注意点は1)クラスが参照する必要がありますSystem.Windows.Formsがあること。これは、データ抽象化レイヤーでは該当しない場合があります。2)DataGridViewは4.0に存在しないため、アセンブリは.NET 4.5フレームワークを対象にする必要があります。 3)クリップボードが別のプロセスで使用されている場合、メソッドは失敗します。

とにかく、このアプローチはあなたの状況に適していないかもしれませんが、それでもなお興味深いものであり、ツールボックスの別のツールになる可能性があります。


1
クリップボードを使用する必要はありませんstackoverflow.com/questions/40726017/….GetClipboardContentまた、を含む値の少数のエッジケースも処理します,"\t(タブをスペースに変換します)
Slai

2
これは良いことですが、誰かが同時にマシンを使用していて、重要な瞬間にクリップボードに何かを入れたらどうなるでしょうか。
Ayo Adesina 2017

7

私は最近これを行いましたが、私の値を二重引用符で囲みました。

たとえば、次の2行を変更します。

sb.Append("\"" + col.ColumnName + "\","); 
...
sb.Append("\"" + row[i].ToString() + "\","); 

提案に感謝しますが、すべてのデータはまだ各行の最初のセル内にありますか?
ダレンヤング

7

に変更sb.Append(Environment.NewLine);してみてくださいsb.AppendLine();

StringBuilder sb = new StringBuilder();          
foreach (DataColumn col in dt.Columns)         
{             
    sb.Append(col.ColumnName + ',');         
}          

sb.Remove(sb.Length - 1, 1);         
sb.AppendLine();          

foreach (DataRow row in dt.Rows)         
{             
    for (int i = 0; i < dt.Columns.Count; i++)             
    {                 
        sb.Append(row[i].ToString() + ",");             
    }              

    sb.AppendLine();         
}          

File.WriteAllText("test.csv", sb.ToString());

その場合、2つの復帰が返されます。
ダレンヤング

@alexl:それは私がもともと行っていたものですが、VSが起動するまで頭の上から離れていました:o)
Neil Knight


5

これを読んでこれますか?


より良い実装は

var result = new StringBuilder();
for (int i = 0; i < table.Columns.Count; i++)
{
    result.Append(table.Columns[i].ColumnName);
    result.Append(i == table.Columns.Count - 1 ? "\n" : ",");
}

foreach (DataRow row in table.Rows)
{
    for (int i = 0; i < table.Columns.Count; i++)
    {
        result.Append(row[i].ToString());
        result.Append(i == table.Columns.Count - 1 ? "\n" : ",");
    }
}
 File.WriteAllText("test.csv", result.ToString());

5

エラーはリスト区切り文字です。

書く代わりに sb.Append(something... + ',')あなたは次のようなものを置くべきですsb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);

オペレーティングシステムで構成されたリスト区切り文字(上記の例のように)、またはファイルを監視するクライアントマシンにリスト区切り文字を配置する必要があります。別のオプションは、app.configまたはweb.configでアプリケーションのパラメーターとして構成することです。


5

4行のコード:

public static string ToCSV(DataTable tbl)
{
    StringBuilder strb = new StringBuilder();

    //column headers
    strb.AppendLine(string.Join(",", tbl.Columns.Cast<DataColumn>()
        .Select(s => "\"" + s.ColumnName + "\"")));

    //rows
    tbl.AsEnumerable().Select(s => strb.AppendLine(
        string.Join(",", s.ItemArray.Select(
            i => "\"" + i.ToString() + "\"")))).ToList();

    return strb.ToString();
}

ToList()最後のが重要であることに注意してください。表情評価を強制する何かが必要です。コードゴルフなら、Min()代わり。

また、への最後の呼び出しのために、結果の最後に改行があることに注意してくださいAppendLine()。あなたはこれを望まないかもしれません。呼び出すだけでTrimEnd()削除できます。


3

これは、Excelと同じ方法でカンマを処理するvc-74の投稿の拡張です。Excelは、データにコンマがある場合はデータを引用符で囲みますが、データにコンマがない場合は引用しません。

    public static string ToCsv(this DataTable inDataTable, bool inIncludeHeaders = true)
    {
        var builder = new StringBuilder();
        var columnNames = inDataTable.Columns.Cast<DataColumn>().Select(column => column.ColumnName);
        if (inIncludeHeaders)
            builder.AppendLine(string.Join(",", columnNames));
        foreach (DataRow row in inDataTable.Rows)
        {
            var fields = row.ItemArray.Select(field => field.ToString().WrapInQuotesIfContains(","));
            builder.AppendLine(string.Join(",", fields));
        }

        return builder.ToString();
    }

    public static string WrapInQuotesIfContains(this string inString, string inSearchString)
    {
        if (inString.Contains(inSearchString))
            return "\"" + inString+ "\"";
        return inString;
    }

2

ファイルに書き込むには、次の方法が最も効率的で簡単だと思います(必要に応じて引用符を追加できます)。

public static void WriteCsv(DataTable dt, string path)
{
    using (var writer = new StreamWriter(path)) {
        writer.WriteLine(string.Join(",", dt.Columns.Cast<DataColumn>().Select(dc => dc.ColumnName)));
        foreach (DataRow row in dt.Rows) {
            writer.WriteLine(string.Join(",", row.ItemArray));
        }
    }
}

2

Excel CSVを模倣するには:

public static string Convert(DataTable dt)
{
    StringBuilder sb = new StringBuilder();

    IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                        Select(column => column.ColumnName);
    sb.AppendLine(string.Join(",", columnNames));

    foreach (DataRow row in dt.Rows)
    {
        IEnumerable<string> fields = row.ItemArray.Select(field =>
        {
            string s = field.ToString().Replace("\"", "\"\"");
            if(s.Contains(','))
                s = string.Concat("\"", s, "\"");
            return s;
        });
        sb.AppendLine(string.Join(",", fields));
    }

    return sb.ToString().Trim();
}

1
StringBuilder sb = new StringBuilder();
        SaveFileDialog fileSave = new SaveFileDialog();
        IEnumerable<string> columnNames = tbCifSil.Columns.Cast<DataColumn>().
                                          Select(column => column.ColumnName);
        sb.AppendLine(string.Join(",", columnNames));

        foreach (DataRow row in tbCifSil.Rows)
        {
            IEnumerable<string> fields = row.ItemArray.Select(field =>string.Concat("\"", field.ToString().Replace("\"", "\"\""), "\""));
            sb.AppendLine(string.Join(",", fields));
        }

        fileSave.ShowDialog();
        File.WriteAllText(fileSave.FileName, sb.ToString());

StackOverflowへようこそ!回答は、コードスニペットの説明が含まれている場合に最適です。個人的に、質問と回答の間に変数名が並ぶと、それらがより役立つことがわかりました。
AWinkle

1
public void ExpoetToCSV(DataTable dtDataTable, string strFilePath)
{

    StreamWriter sw = new StreamWriter(strFilePath, false);
    //headers   
    for (int i = 0; i < dtDataTable.Columns.Count; i++)
    {
        sw.Write(dtDataTable.Columns[i].ToString().Trim());
        if (i < dtDataTable.Columns.Count - 1)
        {
            sw.Write(",");
        }
    }
    sw.Write(sw.NewLine);
    foreach (DataRow dr in dtDataTable.Rows)
    {
        for (int i = 0; i < dtDataTable.Columns.Count; i++)
        {
            if (!Convert.IsDBNull(dr[i]))
            {
                string value = dr[i].ToString().Trim();
                if (value.Contains(','))
                {
                    value = String.Format("\"{0}\"", value);
                    sw.Write(value);
                }
                else
                {
                    sw.Write(dr[i].ToString().Trim());
                }
            }
            if (i < dtDataTable.Columns.Count - 1)
            {
                sw.Write(",");
            }
        }
        sw.Write(sw.NewLine);
    }
    sw.Close();
}

1

おそらく、最も簡単な方法は次のようにすることです:

https://github.com/ukushu/DataExporter

特に、/r/ndataTableセル内に文字または区切り記号を含むデータテーブルのデータの場合。他のほとんどすべての回答は、このようなセルでは機能しません。

必要なのは次のコードを書くことだけです:

Csv csv = new Csv("\t");//Needed delimiter 

var columnNames = dt.Columns.Cast<DataColumn>().
    Select(column => column.ColumnName).ToArray();

csv.AddRow(columnNames);

foreach (DataRow row in dt.Rows)
{
    var fields = row.ItemArray.Select(field => field.ToString()).ToArray;
    csv.AddRow(fields);   
}

csv.Save();

0

他の誰かがこれにつまずく場合に備えて、私はFile.ReadAllTextを使用してCSVデータを取得し、それを変更してFile.WriteAllTextで書き戻しました。\ r \ n CRLFは問題ありませんでしたが、Excelで開いたときに\ tタブは無視されました。(これまでのところ、このスレッドのすべてのソリューションではコンマ区切り文字を使用していますが、それは問題ではありません。)メモ帳は、ソースと同じ形式で結果ファイルに表示されました。差分では、ファイルが同一であるとさえ示されていました。しかし、バイナリエディターを使用してVisual Studioでファイルを開くと、手がかりがわかりました。ソースファイルはUnicodeでしたが、ターゲットは ASCIIでした。修正するために、3番目の引数をSystem.Text.Encoding.Unicodeに設定してReadAllTextとWriteAllTextの両方を変更し、そこからExcelが更新されたファイルを開くことができました。


0

FYR

private string ExportDatatableToCSV(DataTable dtTable)
{
    StringBuilder sbldr = new StringBuilder();
    if (dtTable.Columns.Count != 0)
    {
        foreach (DataColumn col in dtTable.Columns)
        {
            sbldr.Append(col.ColumnName + ',');
        }
        sbldr.Append("\r\n");
        foreach (DataRow row in dtTable.Rows)
        {
            foreach (DataColumn column in dtTable.Columns)
            {
                sbldr.Append(row[column].ToString() + ',');
            }
            sbldr.Append("\r\n");
        }
    }
    return sbldr.ToString();
}

0

これが、Paul GrimshawAnthony VOによる以前の回答に基づく私の解決策です。GithubのC#プロジェクトでコードを送信しました

私の主な貢献は、明示的にを作成して操作することを排除しStringBuilder、代わりにでのみ作業することIEnumerableです。これにより、メモリ内の大きなバッファの割り当てが回避されます。

public static class Util
{
    public static string EscapeQuotes(this string self) {
        return self?.Replace("\"", "\"\"") ?? "";
    }

    public static string Surround(this string self, string before, string after) {
        return $"{before}{self}{after}";
    }

    public static string Quoted(this string self, string quotes = "\"") {
        return self.Surround(quotes, quotes);
    }

    public static string QuotedCSVFieldIfNecessary(this string self) {
        return (self == null) ? "" : self.Contains('"') ? self.Quoted() : self; 
    }

    public static string ToCsvField(this string self) {
        return self.EscapeQuotes().QuotedCSVFieldIfNecessary();
    }

    public static string ToCsvRow(this IEnumerable<string> self){
        return string.Join(",", self.Select(ToCsvField));
    }

    public static IEnumerable<string> ToCsvRows(this DataTable self) {          
        yield return self.Columns.OfType<object>().Select(c => c.ToString()).ToCsvRow();
        foreach (var dr in self.Rows.OfType<DataRow>())
            yield return dr.ItemArray.Select(item => item.ToString()).ToCsvRow();
    }

    public static void ToCsvFile(this DataTable self, string path) {
        File.WriteAllLines(path, self.ToCsvRows());
    }

}

このアプローチは、ここで尋ねられるようIEnumerableにDataTableへの変換とうまく組み合わされます。


0
        DataTable dt = yourData();
        StringBuilder csv = new StringBuilder();
        int dcCounter = 0;

        foreach (DataColumn dc in dt.Columns)
        {
            csv.Append(dc);
            if (dcCounter != dt.Columns.Count - 1)
            {
                csv.Append(",");
            }
            dcCounter++;
        }
        csv.AppendLine();

        int numOfDc = dt.Columns.Count;
        foreach (DataRow dr in dt.Rows)
        {
            int colIndex = 0;
            while (colIndex <= numOfDc - 1)
            {
                var colVal = dr[colIndex].ToString();
                if (colVal != null && colVal != "")
                {
                    DateTime isDateTime;
                    if (DateTime.TryParse(colVal, out isDateTime))
                    {
                        csv.Append(Convert.ToDateTime(colVal).ToShortDateString());
                    }
                    else
                    {
                        csv.Append(dr[colIndex]);
                    }
                }
                else
                {
                    csv.Append("N/A");
                }
                if (colIndex != numOfDc - 1)
                {
                    csv.Append(",");
                }
                colIndex++;
            }
            csv.AppendLine();

また、データを上書きする必要があったため、「if else」ステートメントがいくつかあります。代わりに「N / A」を入力するためにフィールドが空だった場合、または日付フィールドが「01/01/1900:00」としてフォーマットされている場合、「01/01/1900」として保存されることを確認する必要がありました。代わりに。


0
StringBuilder sb = new StringBuilder();

        foreach (DataColumn col in table.Columns)
        {
            sb.Append(col.ColumnName + ";");
        }

        foreach (DataRow row in table.Rows)
        {
            sb.AppendLine();
            foreach (DataColumn col in table.Columns)
            {
                sb.Append($@"{Convert.ToString(row[col])}" + ";");
            }
        }
        File.WriteAllText(path, sb.ToString());

-1

すべてのデータがまだ最初のセルにある場合は、ファイルを開いたアプリケーションが別の区切り文字を予期していることを意味します。MSExcelは、特に指定しない限り、コンマを区切り文字として処理できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.