У меня есть
DataTable Adress_for_mapping
Необходимо записать её в CSV файл построчно, потому что если записывать целый документ, то не хватает памяти для этого - OutOfMemoryException. Пробую это сделать вот так, всё работает, но очень долго:
StringBuilder sb = new StringBuilder();
string[] columnNames = Adress_for_mapping.Columns.Cast<DataColumn>().
Select(column => column.ColumnName).
ToArray();
sb.AppendLine(string.Join(";", columnNames));
File.AppendAllText(@".\Res_mapping.csv", sb.ToString(), Encoding.GetEncoding(1251));
sb.Clear();
foreach (DataRow row in Adress_for_mapping.Rows)
{
string[] fields = row.ItemArray.Select(field => field.ToString()).
ToArray();
sb.AppendLine(string.Join(";", fields));
File.AppendAllText(@".\Res_mapping.csv", sb.ToString(), Encoding.GetEncoding(1251));
sb.Clear();
}
А почему бы полностью не избавиться от StringBuilder? Пишем сразу в файл.
Благодаря некоторым допущениям получаем максимально компактный код.
using (var streamWriter = new StreamWriter(@".\Res_mapping.csv", false, Encoding.GetEncoding(1251)))
{
var columns = Adress_for_mapping.Columns.Cast<DataColumn>();
streamWriter.WriteLine(string.Join(";", columns));
foreach (DataRow row in Adress_for_mapping.Rows)
{
streamWriter.WriteLine(string.Join(";", row.ItemArray));
}
}
Метод DataColumn.ToString выдает имя колонки, так что можно убрать Select. Однако, если среди колонок есть вычисляемые, с непустым свойством Expression, тогда Select нужен. Вызов ToArray тоже не обязателен, т. к. одна из перегрузок string.Join принимает IEnumerable.
Метод string.Join также принимает на вход массив объектов, между тем DataRow.ItemArray им и является, так что лишние преобразования не нужны.
Код легко преобразовать в асинхронный, использовав методы WriteLineAsync (не забываем async/await где нужно).
Можно ещё сократить потребление памяти (вернее, облегчить работу сборщика мусора), избавившись от создания строк с помощью string.Join. Для этого используем метод Write - пишем каждый элемент сразу в файл. Естественно, код увеличится в размере.
Теперь немного критики.
Не используйте однобайтовую кодировку. Ведь если в ваших данных окажутся символы, не входящие в набор символов этой кодировки, то они в результате исчезнут. Используйте юникод: UTF-8 или UTF-16.
Что за название такое: Adress_for_mapping? Ознакомьтесь: Naming Guidelines, ещё сслыка.
Этот код, так же, как и ваш, приведёт к неправильному результату, если в данных содержится символ-разделитель (в данном случае ;). Нужно обрамлять такие поля в кавычки. Или взять стороннюю библиотеку, как предлагает Andrew, чтобы не мучаться вручную.
По поводу долгой работы кода. Если данных много - ничего с этим не поделать. Мы ограничены скоростью процессора и (в большей мере) скоростью накопителя.
На заметку: DataTable тоже весьма не быстр. Если есть возможность - переходите на коллекции строго типизированных объектов.
Зато учитывает полностью все подводные камни использования CSV формата. Например, экранирование строк-ячеек которые имеют знак-разделитель внутри или переход на следующую строку.... Что долго и муторно учитывать при собственной ручной реализации чтения CSV.
можно взять библиотеку с ответа: Как просто работать с / открыть / изменить / сохранить Excel - xlsx / CSV файлы
и тогда выйдет что-то вроде:
Csv csv = new Csv(';'); //раз уж в твоем примере использовался этот разделитель
string[] columnNames = Adress_for_mapping.Columns.Cast<DataColumn>().
Select(column => column.ColumnName).ToArray();
csv.AddRow(columnNames);
foreach (DataRow row in Adress_for_mapping.Rows)
{
string[] fields = row.ItemArray.Select(field => field.ToString()).ToArray();
csv.AddRow(fields);
}
csv.FileSave("c:\\file2.csv");
Накатал на основе кода из вопроса
private void SaveDgvToFile(DataGridView dgv, char separator, string filePath)
{
ClearFile(filePath);
StringBuilder sb = new StringBuilder();
sb.AppendLine( GetDgvHeadersLine(dgv, separator) );
for (int i = 0; i<= dgv.Rows.Count; i++)
{
sb.AppendLine( GetDgvDataStr(dgv, separator, i) );
if (i%1000 == 0)// сохраняем каждые 1000 строк
{
AppendToFile(sb, string filePath);
sb.Clear();
}
}
AppendToFile(sb, string filePath);//сохраняем остаток
}
private string GetDgvHeadersLine(DataGridView dgv, char separator)
{
string[] columnNames = dgv.Columns.Cast<DataColumn>().
Select(column => column.ColumnName).ToArray();
return string.Join(separator, columnNames);
}
private string GetDgvDataStr(DataGridView dgv, char separator, int index)
{
string[] fields = dgv.Rows[index].ItemArray.
Select(field => field.ToString()).ToArray();
return string.Join(separator, columnNames);
}
private void AppendToFile(StringBuilder sb, string filePath)
{
File.AppendAllText(filePath, sb.ToString(), Encoding.GetEncoding(1251));
}
private void ClearFile(string filePath)
{
System.IO.File.WriteAllText(filePath,string.Empty);
}
Код писал из головы в блокнот не проверяя. Так что могут быть синтаксические и мелкие логические ошибки.
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости