FirstOrDefault () добавление дней времени к итерации - PullRequest
0 голосов
/ 06 декабря 2018

Здесь есть некий крайний случай.Мне было поручено перенести все данные из одной базы данных в другую, где база данных назначения имеет другую схему.

Я решил написать утилиту WinForms для отображения и передачи данных при необходимости с помощью Entity Framework / ADO.NET.

До сих пор это прекрасно работало, за исключением одной конкретной таблицы с 2,5 миллионами записей.Передача занимает около 10 минут, когда я игнорирую все внешние ключи, однако, когда я начинаю отображать внешние ключи с помощью FirstOrDefault() вызовов в списках данных, которые уже были перемещены в целевую базу данных, буквально 4 дня добавляются кколичество времени, которое требуется.

Мне нужно много раз запустить этот инструмент в ближайшие дни, так что это не совсем приемлемо для меня.

Вот мой текущий подход (Не мой первый подход, эторезультат большого количества проб и ошибок ради эффективности):

private OldModelContext _oldModelContext { get; } //instantiated in controller

using (var newModelContext = new NewModelContext())
    {
        //Takes no time at all to load these into memory, collections are small, 3 - 20 records each
        var alreadyMigratedTable1 = newModelContext.alreadyMigratedTable1.ToList();
        var alreadyMigratedTable2 = newModelContext.alreadyMigratedTable2.ToList();
        var alreadyMigratedTable3 = newModelContext.alreadyMigratedTable3.ToList();
        var alreadyMigratedTable4 = newModelContext.alreadyMigratedTable4.ToList();
        var alreadyMigratedTable5 = newModelContext.alreadyMigratedTable5.ToList();

        var oldDatasetInMemory = _oldModelContext.MasterData.AsNoTracking().ToList();//2.5 Million records, takes about 6 minutes 

        var table = new DataTable("MasterData");
        table.Columns.Add("Column1");
        table.Columns.Add("Column2");
        table.Columns.Add("Column3");
        table.Columns.Add("ForeignKeyColumn1");
        table.Columns.Add("ForeignKeyColumn2");
        table.Columns.Add("ForeignKeyColumn3");
        table.Columns.Add("ForeignKeyColumn4");
        table.Columns.Add("ForeignKeyColumn5");

        foreach(var masterData in oldDatasetInMemory){
            DataRow row = table.NewRow();

            //With just these properties mapped, this takes about 2 minutes for all 2.5 Million
            row["Column1"] = masterData.Property1;
            row["Column2"] = masterData.Property2;
            row["Column3"] = masterData.Property3;

            //With this mapping, we add about 4 days to the overall process.
            row["ForeignKeyColumn1"] = alreadyMigratedTable1.FirstOrDefault(s => s.uniquePropertyOnNewDataset == masterData.uniquePropertyOnOldDataset);
            row["ForeignKeyColumn2"] = alreadyMigratedTable2.FirstOrDefault(s => s.uniquePropertyOnNewDataset == masterData.uniquePropertyOnOldDataset);
            row["ForeignKeyColumn3"] = alreadyMigratedTable3.FirstOrDefault(s => s.uniquePropertyOnNewDataset == masterData.uniquePropertyOnOldDataset);
            row["ForeignKeyColumn4"] = alreadyMigratedTable4.FirstOrDefault(s => s.uniquePropertyOnNewDataset == masterData.uniquePropertyOnOldDataset);
            row["ForeignKeyColumn5"] = alreadyMigratedTable5.FirstOrDefault(s => s.uniquePropertyOnNewDataset == masterData.uniquePropertyOnOldDataset);

            table.Rows.Add(row);
        }   

        //Save table with SQLBulkCopy is very fast, takes about a minute and a half.
    }
}

Примечание: uniquePropertyOn(New/Old)Dataset чаще всего является уникальной строкой описания, разделяемой между наборами данных, не может соответствовать идентификаторам, поскольку они не будутто же самое для баз данных.

Я пытался:

  1. Вместо использования foreach, приведенного с помощью оператора linq select, улучшения не было.
  2. Использовать .Where(predicate).FirstOrDefault(), значительного улучшения не наблюдалось
  3. Запуск FirstOrDefault() для iqueryable вместо списков перенесенных данных, улучшения не наблюдалось.
  4. Отображение в список вместодата, но это не имеет никакого значения в скорости отображения, а также делает объемные сохранения медленнее.

Я возился с идеей превратить foreach в параллельный цикл foreach и заблокировать вызовы для данных, но я продолжаю сталкиваться с

Закрытие соединения с Entity Framework

при запросе списков в памяти при использовании параллельного foreach ... не совсем уверен, о чем идет речь, но изначально результаты скорости были многообещающими.

Я был бы рад опубликовать этот код / ​​ошибки, если кто-то считает, что это правильный путь, но я не уверен больше ..

Ответы [ 2 ]

0 голосов
/ 06 декабря 2018

Если на самом деле нет другого способа перенести эти данные, кроме как загрузить все данные в память, вы можете сделать их более эффективными, избегая этого вложенного цикла и связывая списки с помощью Join.

Чтение: Почему LINQ JOIN намного быстрее, чем соединение с WHERE?

var newData =
    from master in oldDatasetInMemory
    join t1 in alreadyMigratedTable1
        on master.uniquePropertyOnOldDataset equals t1.uniquePropertyOnNewDataset into t1Group
    from join1 in t1Group.Take(1).DefaultIfEmpty()
    join t2 in alreadyMigratedTable2
        on master.uniquePropertyOnOldDataset equals t2.uniquePropertyOnNewDataset into t2Group
    from join2 in t2Group.Take(1).DefaultIfEmpty()
    join t3 in alreadyMigratedTable3
        on master.uniquePropertyOnOldDataset equals t3.uniquePropertyOnNewDataset into t3Group
    from join3 in t1Group.Take(1).DefaultIfEmpty()
    join t4 in alreadyMigratedTable4
        on master.uniquePropertyOnOldDataset equals t4.uniquePropertyOnNewDataset into t4Group
    from join4 in t1Group.Take(1).DefaultIfEmpty()
    join t5 in alreadyMigratedTable5
        on master.uniquePropertyOnOldDataset equals t5.uniquePropertyOnNewDataset into t5Group
    from join5 in t1Group.Take(1).DefaultIfEmpty()
    select new { master, join1, join2, join3, join4, join5};

foreach (var x in newData)
{
    DataRow row = table.Rows.Add();
    row["Column1"] = x.master.Property1;
    row["Column2"] = x.master.Property2;
    row["Column3"] = x.master.Property3;
    row["ForeignKeyColumn1"] = x.join1;
    row["ForeignKeyColumn2"] = x.join2;
    row["ForeignKeyColumn3"] = x.join3;
    row["ForeignKeyColumn4"] = x.join4;
    row["ForeignKeyColumn5"] = x.join5;
}

Это левостороннее соединение LINQ, которое занимает только один ряд с правой стороны.

0 голосов
/ 06 декабря 2018

Первое, что я попробую, - это словарь и предварительная выборка столбцов:

var fk1 = oldDatasetInMemory.Columns["ForeignKeyColumn1"];

// ...

var alreadyMigratedTable1 = newModelContext.alreadyMigratedTable1.ToDictionary(
    x => x.uniquePropertyOnNewDataset);

// ...

if (alreadyMigratedTable1.TryGetValue(masterData.uniquePropertyOnOldDataset, out var val))
    row[fk1] = val;

Однако, на самом деле: я бы также попытался избежать всего фрагмента DataTable, если только он действительно, действительно необходимо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...