【问题标题】:How can I run this entity framework sample faster如何更快地运行此实体框架示例
【发布时间】:2016-09-19 08:37:36
【问题描述】:

我有这段代码用于向数据库添加大约 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,它不会冻结 UI。现在我的问题是:如何改进此代码以更快地运行。我如何在这里使用并行编程?你能告诉我如何使用 TPL 的例子吗?

编辑

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

【问题讨论】:

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


【解决方案1】:

并行处理在这里不会有太大帮助,因为您的循环本身根本不需要时间。所有消耗的时间都归结为两件事:

  1. 当您将新项目添加到实体框架上下文时(据我了解,您在此处使用 EF)- 它对上下文中的许多项目(不仅仅是您添加的项目)执行一些操作,并且速度越来越慢您添加的项目越多。

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

这里最简单的做法是减少上面列表中第 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 代替。

【讨论】:

  • 从未想过 AddRange 技巧。我所知道的就是按照@Nasreddine 的建议去做。也会试试这个。
  • 谢谢。我只想知道我的代码在 TPL 或并行处理中的等价物是什么?不管它是否有帮助,只是想知道我如何通过 TPL 做到这一点?
  • 即使使用列表也需要 6 秒。
  • 我已经对此进行了测试,使用 AddRange 而不是在循环中添加 Add,我需要 700 毫秒,而原始代码需要 6.6 秒。
  • 没有。 AddRange 和 Add 对我来说没有区别,至少需要 3 秒以上。为什么?
【解决方案2】:

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

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

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

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

【讨论】:

  • 谢谢。请在其他答案下方阅读我的 cmets。 AddRange 和 Add 和 AutoDetectChangesEnabled 没有区别,至少需要 3 秒以上。为什么?
  • 正如我所说,优化 ef 代码能做的只有这么多。如果 3 秒对你来说是不可接受的,你应该开始研究 ADO.Net。
  • 我真的不明白。为什么 ADO.NET 应该比 Entity Framework 更快?
  • 请参阅this 其他问题,该问题的详细信息超出了我的评论范围。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多