如何更快地运行此实体框架示例

时间:2016-05-22 12:40:05

标签: c# multithreading entity-framework parallel-processing async-await

我有这个代码用于向数据库添加大约1500条记录:

async void button7_Click(object sender, EventArgs e)
{
    var task = await Task.Run(() =>
    {
        Random rnd = new Random();
        for (int i = 0; i <= 1500; i++)
        {
            db.Tbls.Add(new Tbl()
            {
                Name = "User" + i + 1,
                Num = rnd.Next(10, i + 10) / 10
            });
            progress.Report(i * 100 / 1500);
        }

        db.SaveChanges();
        return db.Tbls.Count();

    });
}

但是完成这个过程需要大约4秒钟,但因为我使用了async/await它并没有冻结用户界面。现在我的问题是:如何改进此代码以更快地运行。我如何在这里使用并行编程?你能告诉我一个如何使用TPL的例子吗?

修改

我按照建议使用并行循环和AddRange,但它没有效果。在所有建议的方式中,我的过程仍然需要大约4秒钟。如果有人能帮助我解决这个问题,我真的很感激。

2 个答案:

答案 0 :(得分:3)

并行处理在这里不会有很大的帮助,因为你的循环本身就没有时间。所有消耗的时间都分为两个方面:

  1. 当您向Entity Framework上下文添加新项目(并且您在此处使用EF)时,它会对上下文中的许多项目执行某些操作(而不仅仅是您添加的项目)并且它会变得越来越慢您添加的项目越多。

  2. Perfoming 1500数据库插入也需要一些时间。

  3. 这里最简单的方法是减少上面列表中第1点所花费的时间。你可以这样做:

    async void button7_Click(object sender, EventArgs e)
    {
        var task = await Task.Run(() =>
        {
            Random rnd = new Random();
            var tbls = new List<Tbl>();
            for (int i = 0; i <= 1500; i++)
            {
                tbls.Add(new Tbl()
                {
                    Name = "User" + i + 1,
                    Num = rnd.Next(10, i + 10) / 10
                });
                progress.Report(i * 100 / 1500);
             }
            db.Tbls.AddRange(tbls);
            db.SaveChanges();
            return db.Tbls.Count();
    
        });
    }
    

    使用AddRange并首先收集简单List中的所有项目,您将大大减少代码消耗的时间,至少低于1秒。

    更新。如果你想使用并行循环,即使这不会有帮助,你也可以这样做:

    int seed = Environment.TickCount;
    var random = new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref seed)));
    var tbls = new ConcurrentBag<Tbl>();
    Parallel.For(0, 1500, (i) => {
        tbls.Add(new Tbl()
        {
            Name = "User" + i + 1,
            Num = random.Value.Next(10, i + 10) / 10
        });
    });                
    db.Tbls.AddRange(tbls);
    

    注意事项:

    • Random不是线程安全的,所以我们使用线程局部实例(并行循环内的每个线程一个),每个实例都有不同的种子值。
    • List不是线程安全的 - 我们使用ConcurrentBag代替。

答案 1 :(得分:3)

如果性能是目标,那么您应该使用EF以外的其他内容,例如dapper(StackOverflow.com使用的相同内容)或原始ADO.Net。除此之外,您可以通过停用change auto detectionvalidation on save来提高实体框架的效果:

yourDbContext.Configuration.AutoDetectChangesEnabled = false;
yourDbContext.Configuration.ValidateOnSaveEnabled = false;

您应该将上述代码与使用AddRange的@Evk建议结合起来。

另一种可能有助于加快插入速度的解决方案是使用BulkInsert(图片来自here):

enter image description here