Лучший способ вставить миллионы записей - PullRequest
0 голосов
/ 31 января 2020

Я работаю с размещенным сервисом в C# asp. net core, linQ и T- SQL.

Мне нужно сделать вставку одну за одной из записей в моя база данных Конечно, это не быстрая операция, но я не настолько опытен в этой области, поэтому, возможно, я делаю это неправильно. Это мой код в моем менеджере:

    public void StrategyMassive(string foldpathsave)
    {
        using (IServiceScope scope = _services.CreateScope())
        {
            List<string> filesreading = new List<string>();

            VUContext _context = scope.ServiceProvider.GetRequiredService<VUContext>();

            List<string> filesnumber = File.ReadAllLines(foldpathsave).ToList();

            filesreading = filesnumber.ToList();

            filesreading.RemoveRange(0, 2);

            foreach (string singlefile in filesreading)
            {

                //INTERNAL DATA NORMALIZATION

                _repository.ImportAdd(_context, newVUL, newC2, newC3, newDATE);

                _repository.Save(_context);

            }
        }
    }

А это мой интерфейс репозитория:

    public void ImportAdd(VUContext _context, AVuTable newVUL, ACs2Table newC2, ACs3Table newC3, ADateTable newDATe)
    {
        _context.AVuTable.Add(newVU);

        _context.ADateTable.Add(newDATE);

        if (newC2 != null)
        {
            _context.ACs2Table.Add(newC2);
        }

        if (newC3 != null)
        {
            _context.ACs3Table.Add(newC3);
        }

        public void Save(VUContext _context)

        {
            _context.SaveChanges();
        }
    }

Это все довольно просто, я знаю, так как я могу ускорить эту вставку, сохраняя ее легко по одной записи?

Ответы [ 3 ]

2 голосов
/ 31 января 2020

По моему опыту, SqlBulkCopy - самый быстрый способ сделать это. Filesnumber кажется неправильным, и я подозреваю, что вы читаете список файлов с разделителями, которые будут загружены на SQL Сервер после некоторого процесса нормализации. Вероятно, это будет даже быстрее, если вы выполните нормализацию на стороне сервера после первоначальной загрузки данных во временный файл. Вот пример SqlBulkCopy из файла с разделителями:

void Main()
{
  Stopwatch sw = new Stopwatch();
  sw.Start();
  string sqlConnectionString = @"server=.\SQLExpress2012;Trusted_Connection=yes;Database=SampleDb";

  string path = @"d:\temp\SampleTextFiles";
  string fileName = @"combDoubledX.csv";

  using (OleDbConnection cn = new OleDbConnection(
    "Provider=Microsoft.ACE.OLEDB.12.0;Data Source="+path+
    ";Extended Properties=\"text;HDR=No;FMT=Delimited\";"))

  using (SqlConnection scn = new SqlConnection( sqlConnectionString ))
  {
  OleDbCommand cmd = new OleDbCommand("select * from "+fileName, cn);

  SqlBulkCopy sbc = new SqlBulkCopy(scn, SqlBulkCopyOptions.TableLock,null);

  sbc.ColumnMappings.Add(0,"[Category]");
  sbc.ColumnMappings.Add(1,"[Activity]");
  sbc.ColumnMappings.Add(5,"[PersonId]");
  sbc.ColumnMappings.Add(6,"[FirstName]");
  sbc.ColumnMappings.Add(7,"[MidName]");
  sbc.ColumnMappings.Add(8,"[LastName]");
  sbc.ColumnMappings.Add(12,"[Email]");

  cn.Open();
  scn.Open();

  SqlCommand createTemp = new SqlCommand();
  createTemp.CommandText = @"if exists
   (SELECT * FROM tempdb.sys.objects 
   WHERE object_id = OBJECT_ID(N'[tempdb]..[##PersonData]','U'))
   BEGIN
        drop table [##PersonData];
   END

  create table ##PersonData 
  (
    [Id] int identity primary key,
    [Category] varchar(50), 
    [Activity] varchar(50) default 'NullOlmasin', 
    [PersonId] varchar(50), 
    [FirstName] varchar(50), 
    [MidName] varchar(50), 
    [LastName] varchar(50), 
    [Email] varchar(50)
  )
"; 
  createTemp.Connection = scn;
  createTemp.ExecuteNonQuery();

  OleDbDataReader rdr = cmd.ExecuteReader();

  sbc.NotifyAfter = 200000;
  //sbc.BatchSize = 1000;
  sbc.BulkCopyTimeout = 10000;
  sbc.DestinationTableName = "##PersonData";
  //sbc.EnableStreaming = true;

  sbc.SqlRowsCopied += (sender,e) =>
    {
    Console.WriteLine("-- Copied {0} rows to {1}.[{2} milliseconds]", 
      e.RowsCopied, 
      ((SqlBulkCopy)sender).DestinationTableName,
      sw.ElapsedMilliseconds);
    };

  sbc.WriteToServer(rdr);

  if (!rdr.IsClosed) { rdr.Close(); }

  cn.Close();
  scn.Close();
  }
  sw.Stop();
  sw.Dump();
}

И несколько примеров строк из этого файла:

"Computer Labs","","LRC 302 Open Lab","","","10057380","Test","","Cetin","","5550123456","","cb@nowhere.com"
"Computer Labs","","LRC 302 Open Lab","","","123456789","John","","Doe","","5551234567","","jdoe@somewhere.com"
"Computer Labs","","LRC 302 Open Lab","","","012345678","Mary","","Doe","","5556666444","","mdoe@here.com"

Вы можете создать и запустить список задач <>, выполняющих чтение SqlBulkCopy из источника (SqlBulkCopy поддерживает серию читателей).

2 голосов
/ 31 января 2020

Начинайте НЕ использовать самый медленный способ сделать это.

Все начинается с того, как вы на самом деле загружаете файлы. Он продолжается, не используя SqlBulkCopy - возможно, в нескольких потоках - для записи данных в базу данных.

То, что вы делаете, - это самый медленный из возможных способов, поскольку EntityFramework НЕ является инструментом ETL.

Кстати, одна транзакция на элемент (SaveChanges) тоже не помогает. Это делает очень медленное решение действительно очень медленным.

Мне удается набрать около 64 000 строк в секунду на поток, при этом 4-6 потоков работают параллельно.

1 голос
/ 31 января 2020

Для ускорения работы вам нужно уменьшить количество обращений к базе данных

Использование пакетной обработки операторов в EF Core

Эта функция доступна только в EF Core, поэтому вам нужно перейти на использование EF Core, если вы все еще используете EF 6.

Сравнить EF Core и EF6

Чтобы эта функция работала, вам нужно чтобы переместить операцию сохранения за пределы l oop.

Массовая вставка

Функция массовой вставки предназначена для того, чтобы обеспечить самый быстрый способ вставки большого количества записей базы данных

Операции группового копирования на SQL Сервер

Чтобы использовать его, вам нужно использовать класс SqlBulkCopy для SQL Сервер, и ваш код требует значительных переделок.

...