我正在使用Entity Framework,我有一个列表,我需要迭代才能完成工作。我无法直接通过数据库查询来完成这项工作,列表可能很安静,所以我希望我可以使用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似乎有时甚至是null,即使它在List中不为null。
答案 0 :(得分:0)
首先让我们谈谈这个问题。 我可以成像的唯一原因是处理 DbContext befour .ForAll 已完成其工作,可能来自不同的线程?但我现在只是在猜测。
我可以为您提供有关可能优化代码的几点建议。 首先使用 .ToList()评估所有报价,如果数据库中有很多记录,可能会对性能产生一些影响,所以我的建议是用SQL游标交换热切的评估。 这可以通过使用任何内部使用 IEnumerable&lt;&gt; 和更具体的 .GetEnumerator()的构造/代码来实现。
IQueriable&lt;&gt;的内部实施在EF中创建一个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();