首页 > 解决方案 > FirstOrDefault() 为迭代添加天数

问题描述

这里有一种边缘案例问题。我的任务是将所有数据从一个数据库拉到另一个数据库,其中目标数据库具有不同的架构。

我选择编写一个 WinForms 实用程序来在必要时使用 Entity Framework/ADO.NET 进行数据映射和传输。

到目前为止,这工作得很好,除了这个有 250 万条记录的特定表。当我忽略所有外键时,传输总共大约需要 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通常是数据集之间共享的唯一描述字符串,不能匹配 Id,因为它们在数据库中不会相同。

我努力了:

  1. 没有使用 foreach,而是使用 linqselect语句进行强制转换,并没有太大的改进。
  2. 使用.Where(predicate).FirstOrDefault(),没看到明显提升
  3. 针对 iqueryable 而不是迁移数据列表运行FirstOrDefault(),没有看到任何改进。
  4. 映射到列表而不是数据表,但这对映射速度没有影响,并且也会使批量保存速度变慢。

我一直在考虑将foreachforeach 转换为并行 foreach 循环并锁定对数据表的调用,但我一直遇到

实体框架连接关闭问题

在使用并行 foreach 时查询内存列表时......不太确定那是什么,但最初的速度结果很有希望。

如果有人认为这是正确的道路,我很乐意发布该代码/错误,但我不确定了..

标签: c#entity-frameworkado.net

解决方案


我要尝试的第一件事是字典,并预取列:

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然而,在现实中:除非真的非常有必要,否则我也会尽量避免整篇文章。


推荐阅读