代码之家  ›  专栏  ›  技术社区  ›  Halter

FirstOrDefault()向迭代添加天数

  •  2
  • Halter  · 技术社区  · 6 年前

    这里有一个边缘案件的问题。我的任务是将所有数据从一个数据库拉到另一个数据库,目标数据库有不同的模式。

    我选择编写一个WinForms实用程序,在必要时使用Entity Framework/a do.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,而是用linq select 声明,没有多少改进。
    2. 使用 .Where(predicate).FirstOrDefault() ,没有明显的改善
    3. 跑步 第一个默认值() 对于iqueryable而不是迁移数据的列表,没有看到任何改进。
    4. 映射到列表而不是数据表,但这不会影响映射速度,也会使大容量存储速度变慢。

    我一直在胡思乱想 foreach 进入一个并行foreach循环并锁定对datatable的调用,但是

    实体框架连接关闭问题

    当使用parallel foreach查询内存中的列表时。。。。不太确定这是什么,但最初的速度结果是有希望的。

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

    2 回复  |  直到 6 年前
        1
  •  3
  •   Marc Gravell    6 年前

    我要做的第一件事是查字典,并预先提取列:

    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 一块除非是 真的,真的 必要的。

        2
  •  2
  •   Tim Schmelter    6 年前

    如果真的没有其他方法可以迁移这些数据,而是将所有内容加载到内存中,那么可以通过避免这个嵌套循环和通过 Join .

    阅读: Why is LINQ JOIN so much faster than linking with 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左外部连接,只从右侧获取一行。