Чтение большого CSV и вставка записей в таблицу БД
Иногда наступает такая ситуация, когда откуда-нибудь приходит огромаднейший CSV-файл, в котором несколько миллионов (это не преувеличение) строк, и надо его содержимое внести в некоторую таблицу нашей БД.
ОС: | Windows 7 SP1 x64 |
Язык: | C# / .NET 4 |
IDE: | Visual Studio |
БД: | Microsoft SQL Server |
Такой файл даже ничем не открыть (кроме Excel
, который, впрочем, сможет показать только первый миллион записей).
Но и ладно, нам открывать его не надо, а чтение и вставку записей в БД будет делать функция, которую мы напишем.
Сразу суть: есть специальный класс по массовой вставке записей в таблицу БД - SqlBulkCopy. Но сложность в том, что если прочитать сразу всё содержимое CSV-файла (который может достигать размера в несколько гигабайт) и попытаться это вставить в БД, то очень легко оперативки не хватит и всё записнет.
Выходом будет использовать некоторый буфер:
- В буфер считывается порция CSV-файла;
- Прочитанное разбирается и вставляется в БД;
- Буфер очищается и всё по новой уже со следующей порцией.
Допустим, наша таблица назначения состоит из трёх полей (колонок):
- Первичный ключ, он нас не интересует, генерируется автоинкрементом;
- Поле
first
; - Поле
second
.
И допустим, файл CSV имеет такой вид:
whore,howmuch
Arlington,300
Fox,900
Goodwin,400
Lawrence,800
...
Первая строка - “шапка таблицы”. Как видим, названия колонок отличаются от наших, потому автобиндинг использовать не будем - далее в коде он закомментирован, а имена колонок прописываются руками.
Вот моё решение.
/// <summary>
/// Чтение CSV-файла и сохранение записей в таблицу БД
/// </summary>
/// <returns>
/// 0 - всё прошло без ошибок
/// 1 - не удалось сохранить записи в БД
/// 2 - ещё что-то не удалось
/// </returns>
int readCSVandSave2DB() // в параметры можно вынести размер буфера и путь до файла
{
// задаём размер буфера, пусть будет 9000 (строк CSV-файла)
int csvBufferSize = 9000;
// указываем путь до CSV-файла
string path2file = @"C:\temp\hugefile.csv";
// сюда он будет читаться
DataTable csvData = new DataTable();
try // пробуем читать
{
// для TextFieldParser надо подключить библиотеку Microsoft.VisualBasic
// но он вовсе не обязателен, CSV в DataTable можете разбирать как хотите
using (TextFieldParser csvReader = new TextFieldParser(path2file))
{
csvReader.SetDelimiters(new string[] { "," });
csvReader.HasFieldsEnclosedInQuotes = true;
string[] colFields = csvReader.ReadFields();
//foreach (string column in colFields) // автобиндинг
//{
// названия колонок в таблице
DataColumn datecolumnSer = new DataColumn("first");
datecolumnSer.AllowDBNull = true;
csvData.Columns.Add(datecolumnSer);
DataColumn datecolumnNum = new DataColumn("second");
datecolumnNum.AllowDBNull = true;
csvData.Columns.Add(datecolumnNum);
//}
int buffer_wannabe = 0;
while (!csvReader.EndOfData)
{
string[] fieldData = csvReader.ReadFields();
csvData.Rows.Add(fieldData);
buffer_wannabe++;
// начитали 9000 строк, заносим их в базу и сбрасываем буфер на 0
if (buffer_wannabe == csvBufferSize)
{
buffer_wannabe = 0;
// функция вставки записей описана ниже
if (!insertCurrentBunchOfRecs(csvData)) return 1;
csvData.Rows.Clear();
}
}
// в последнем чтении в буфере меньше 9000 записей, их тоже надо занести
if (buffer_wannabe != 0)
{
if (!insertCurrentBunchOfRecs(csvData)) return 1;
csvData.Rows.Clear();
}
}
}
catch { return 2; }
// всё огонь
return 0;
}
/// <summary>
/// Сохранение новых записей во временную таблицу в БД
/// </summary>
/// <param name="csvData">строки из CSV</param>
/// <returns></returns>
static private bool insertCurrentBunchOfRecs(DataTable csvData)
{
try
{
using (SqlConnection dbConnection = new SqlConnection("ПОДКЛЮЧЕНИЕКВАШЕЙБД"))
{
dbConnection.Open();
// вся соль вот в этом классе - SqlBulkCopy - он делает всю магию
using (SqlBulkCopy s = new SqlBulkCopy(dbConnection))
{
// если таблица в какой-то схеме, то указать это
s.DestinationTableName = "ИМЯСХЕМЫ.ИМЯТАБЛИЦЫ";
foreach (var column in csvData.Columns)
{
s.ColumnMappings.Add(column.ToString(), column.ToString());
}
s.WriteToServer(csvData);
}
}
}
catch { return false; }
return true;
}
Некоторые фрагменты кода позаимствовал где-то в интернетах, как обычно.
Через три месяца статья получила некоторое продолжение в виде полноценного приложения для разрезания исходного огромного CSV файла на более мелкие, с которыми можно работать не имея гигабайтов оперативки.
Social networks
Zuck: Just ask
Zuck: I have over 4,000 emails, pictures, addresses, SNS
smb: What? How'd you manage that one?
Zuck: People just submitted it.
Zuck: I don't know why.
Zuck: They "trust me"
Zuck: Dumb fucks