Задача: пользователи раз в месяц грузят данные от поставщиков в базу. Файлы приходят разных размеров. от 1 Кб до 500Мб.
Сейчас реализовано работа с файлами по принципу грузим в DataTable и потом пишем в БД. Все хорошо пока пользователи, в количестве 50, не начинают делать это одновременно. На сервере IIS заканчивается память. Возможно загрузка файлов допускает утечку памяти. Пока не получается это выявить.
Есть идея уменьшить нагрузку на IIS, грузить сразу в БД.
Но как всегда есть "но".
Сейчас реализовано через FileStream читаем байты анализируем что прочитали и формируем datatable записывая каждую ячейку по отдельности. и потом через BulkCopy в БД
Если сравнивать производительность с использованием OleDbDataAdapter.Fill То формирование DataTable Проигрывает в два раза медленее.
Если используем SqlBulkCopy.WriteToServer(OleDbDataReader) то производительность по с равнению с имеющимися падает, на примере 250 Мб вместо 10 секунд получаем 15 секунд. но зато выигрываем по памяти.
Как всегда время является критичным...
может кто подскажет что можно со всем этим сделать.
пример с OleDbDataReader
using (OleDbConnection connection = new OleDbConnection($"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={Path.GetDirectoryName(FileName)}; Extended Properties=dBASE IV;"))
{
connection.Open();
var name = Path.GetFileNameWithoutExtension(FileName);
var schem2 = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, new object[] { null, null, name });
List<String> Select = new List<String>();
DataTable DT2 = new DataTable(Table);//Для того чтобы сгенерить скрипт для создания таблицы в базе
foreach (DataRow c in schem2.Rows)
{
DT2.Columns.Add(c["COLUMN_NAME"].ToString(), oleDbToNetTypeConverter((int)c["DATA_TYPE"]));
Select.Add(c["COLUMN_NAME"].ToString());
}
var cmd2 = CreateCommandClass.CreateCreateTableCommand(Table, DT2);
DB.ExecNonQueryWithError(cmd2);
OleDbCommand command = new OleDbCommand($"SELECT {String.Join(",", Select)} FROM {name}", connection);
OleDbDataReader reader = command.ExecuteReader();
using (SqlBulkCopy bulkCopy =
new SqlBulkCopy(UserInfoClass.GetUserInfo().ConnectASPDB))
{
bulkCopy.DestinationTableName = Table;
bulkCopy.BulkCopyTimeout = 360;
try
{
bulkCopy.WriteToServer(reader);
}
catch (Exception ex)
{
err.ErrorMessage = ex.Message;
err.HasError = true;
}
finally
{
reader.Close();
}
}
}
Как работает сейчас
using (FileStream FS = new FileStream(FileName, FileMode.Open))
{
ThrowErrorMessage = "";
try
{
byte[] buffer = new byte[4]; // Кол-во записей: 4 байтa, начиная с 5-го
FS.Position = 4;
FS.Read(buffer, 0, buffer.Length);
int RowsCount = buffer[0] + (buffer[1] * 0x100) + (buffer[2] * 0x10000) + (buffer[3] * 0x1000000); //количество строк
DT.MinimumCapacity = (RowsCount > 50000) ? 50000 : RowsCount;
buffer = new byte[2]; // Кол-во полей: 2 байтa, начиная с 9-го
FS.Position = 8;
FS.Read(buffer, 0, buffer.Length);
int FieldCount = (((buffer[0] + (buffer[1] * 0x100)) - 1) / 32) - 1; //количество полей, каждое описание поля длиной- длиной 32 байта
string[] FieldName = new string[FieldCount]; // Массив названий полей
string[] FieldType = new string[FieldCount]; // Массив типов полей
byte[] FieldSize = new byte[FieldCount]; // Массив размеров полей
byte[] FieldDigs = new byte[FieldCount]; // Массив размеров дробной части
buffer = new byte[32 * FieldCount]; // Описание полей: 32 байтa * кол-во, начиная с 33-го
FS.Position = 32;
FS.Read(buffer, 0, buffer.Length);
int FieldsLength = 0;
for (int i = 0; i < FieldCount; i++)
{
// Заголовки
string titler = System.Text.Encoding.Default.GetString(buffer, i * 32, 10).TrimEnd((char)0x00);
string title2 = "";
int pos = 0;
while (pos < titler.Length && titler[pos] != '\0')
{
title2 += titler[pos++];
}
//переименовать, если колонка с таким нименованием уже есть
if (DT.Columns.Contains(title2))
title2 = title2 + "_" + (i + 1);
if (DT.Columns.Contains(title2))
title2 = title2 + "_" + (i + 1);
//
FieldName[i] = title2;
FieldType[i] = "" + (char)buffer[i * 32 + 11];
FieldSize[i] = buffer[i * 32 + 16];
FieldDigs[i] = buffer[i * 32 + 17];
FieldsLength = FieldsLength + FieldSize[i];
// Создаю колонки
switch (FieldType[i])
{
///Определяем типы
}
if (FieldType[i] != "\0")
{
DT.Columns[FieldName[i]].ExtendedProperties["len"] = FieldSize[i];
DT.Columns[FieldName[i]].ExtendedProperties["declen"] = FieldDigs[i];
}
}
{
FS.ReadByte(); // Пропускаю стартовый байт элемента данных
System.Globalization.DateTimeFormatInfo dfi = new System.Globalization.CultureInfo("en-US", false).DateTimeFormat;
System.Globalization.NumberFormatInfo nfi = new System.Globalization.CultureInfo("en-US", false).NumberFormat;
buffer = new byte[FieldsLength];
int start = 0;
while (start == 0) // выбираем все нулы
start = FS.ReadByte(); // Пропускаю стартовый байт элемента данных
DT.BeginLoadData();
try
{
int FilePos;
if (RowsPortion != 0)
{
RowsCount = RowsPortion;
FilePos = (buffer.Length + 1) * RowsStart;//+1 делаем для того,чтобы учесть пропущенные стартовые байты элементов данных
FS.Position = FS.Position + FilePos + ((RowsStart != 0 && RowsPortion != 0) ? -1 : 0);
}
for (int j = 0; j < RowsCount; j++)//RowsPortion
{
if (j != 0 || (RowsStart != 0 && RowsPortion != 0))
start = FS.ReadByte(); // Пропускаю стартовый байт элемента данных
DataRow R = DT.NewRow();
int Index = 0;
for (int i = 0; i < R.Table.Columns.Count; i++)
{
string l = System.Text.Encoding.GetEncoding(CodePage).GetString(buffer, Index, FieldSize[i]).TrimEnd((char)0x00, (char)0x20);//TrimEnd(new char[] { (char)0x00 }).TrimEnd(new char[] { (char)0x20 });
Index = Index + FieldSize[i];
try
{
if (l != "")
switch (FieldType[i])
{
///записываем значения в соответствии с типом
}
else
R[i] = DBNull.Value;
}
catch { if (l.Contains("")) { } }
}
///Добавляем в DataTable
DT.Rows.Add(R);
}
}
catch (Exception ex)
{
ThrowErrorMessage = RowsStart + "Ошибка при добавлении строки в таблицу (" + DT.Rows.Count + "/" + RowsCount + ")" + ex.Message;
}
finally
{
DT.EndLoadData();
}
}
}
catch (Exception ex)
{
ThrowErrorMessage = "Ошибка при создании таблицы:" + ex.Message;
}
finally
{
FS.Close();
}
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
В одном цикле я постоянно получаю SystemArgumentOutOfRangeException:
Возможна ли передача массива в параметр SQL? Если я хочу массово удалить, или проставить статусы списку заказов, как это сделать?
Есть класс Colum<T>, Есть класс Row со списком List<Colum>И есть проблема, List<Colum> требует указать ещё тип T аки List<Colum<int>>, но мне не нужно...
Я знаю, что Open Server имеет в комплектации консоль, откуда есть доступ к ComposerТем не менее, я бы хотел обойтись без этой консоли и работать с Composer...