【问题标题】:Parallel Foreach with a Lazy Loaded List具有延迟加载列表的并行 Foreach
【发布时间】:2017-01-26 03:06:16
【问题描述】:

我正在使用实体框架,并且我有一个列表,我需要遍历它才能完成工作。我无法直接通过数据库查询来完成这项工作,而且列表可能很大,所以我希望我可以使用 Parallel Foreach 或 AsParallel。

问题是,即使我使用 ToList() 将列表加载到内存中,然后在并行函数中运行它,它也会破坏我延迟加载的导航属性。

我正在运行这样简单的东西(为了这个问题,这已经被简化了很多):

var quoteList = DbContext.Quotes.ToList();

List<QuotesItem> quoteViewItemList = new List<QuotesItem>();

quoteList.ForAll(quote => {
   var quoteViewItem = new QuotesItem();
    quoteViewItem.YearEnding = quote.QuoteService.FirstOrDefault().Yearending; //is SOMETIMES null when it shouldn't be.
    quoteViewItem.Manager  quote.Client.Manager.Name; //IS sometimes null
    quoteViewItem.... = ..... // More processing here
    quoteViewItemList.Add(quoteViewItem);
});

问题是 QuoteService 有时似乎为空,即使它在列表中不为空。

【问题讨论】:

  • quoteList 不是 List&lt;T&gt; 吗?如果是这样,它不会有一个名为QuoteService 的成员。你的意思是quote.QuoteService.....
  • 是的,这是一个错误。我现在正在纠正它。

标签: c# parallel-processing


【解决方案1】:

首先让我们谈谈这个问题。 我能想象的唯一原因是在 .ForAll 完成工作之前处理 DbContext,可能来自不同的线程?但我现在只是猜测。

我可以就可能的代码优化给你一些建议。 如果数据库中有很多记录,首先使用 .ToList() 评估所有 Quotes 可能会对性能产生一些影响,所以我在这里的建议是用 SQL 游标交换急切评估。 这可以通过使用任何内部使用 IEnumerable 和更具体的 .GetEnumerator() 的构造/代码来实现。

在 EF 中 IQueriable 的内部实现会创建一个 SQL 游标,这非常快。最好的部分是您可以将它与 .AsParallel() 或 Parallel.ForEach 一起使用,两者都在内部使用枚举器。

这不是非常有文档记录的功能,但是如果启动 SQL Server Profile 并执行下面的代码,您将看到只向服务器执行了单个请求,并且您还可以注意到执行代码的机器的 RAM 没有达到峰值,这意味着它不会一次获取所有内容。

如果你有兴趣的话,我已经找到了一些关于它的随机信息:https://sqljudo.wordpress.com/2015/02/24/entity-framework-hidden-cursors/

虽然我在数据库记录上操作的代码非常繁重的情况下使用了这种方法,但对于从一种类型到另一种类型的简单投影,使用.AsParallel( .. ) 或简单的foreach 构造运行游标可能会执行类似的操作。

所以使用

DbContext.Quotes
.AsParallel()
.WithDegreeOfParallelism(...)
.ForAll(...)

应该以良好的性能运行。

我的第二个建议是关于使用延迟加载访问 EF 中的导航属性。如您所知,这会导致 N+1 选择问题。 因此,在这种情况下,与其依赖 EF 惰性求值,不如急切地获取导航属性,这样我们就可以重写代码了

 DbContext.Quotes
    .Include(x => x.QuoteService)
    .AsParallel()
    .WithDegreeOfParallelism(...)
    .ForAll(...)

这种方式在访问 QuoteService 导航属性时,EF 不会向服务器发出任何额外请求,因此这应该会显着提高您的性能,并且它可能可以神奇地修复 Null 引用问题(我希望)

.Include(..) 方法的通用版本,我正在使用 System.Data.Entity 命名空间的一部分。

此外,如果您的方案可以接受,您可以禁用更改跟踪,这将为您带来更多性能。所以我的最终代码会是这样的

  var queryViewItems = DbContext.Quotes
            .AsNoTracking()
            .Include(x => x.QuoteService)
            .AsParallel()
            .WithDegreeOfParallelism(...)
            .Select(x => { ... })
            .ToList();

【讨论】:

    猜你喜欢
    • 2013-04-13
    • 1970-01-01
    • 2020-11-05
    • 2019-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多