更新:通过在EF6中添加Inexpression,处理Enumerable.Contains的性能得到了显着提高。 不再需要此答案中描述的方法。
没错,大多数时间都花在处理查询的翻译上。EF的提供程序模型当前不包含表示IN子句的表达式,因此ADO.NET提供程序无法原生支持IN。取而代之的是,Enumerable.Contains的实现将其转换为OR表达式的树,即对于C#中看起来像这样的东西:
new []{1, 2, 3, 4}.Contains(i)
…我们将生成一个Dbexpression树,可以这样表示:
((1 = @i) OR (2 = @i)) OR ((3 = @i) OR (4 = @i))
(必须平衡表达式树,因为如果我们在单个长脊柱上具有所有OR,那么表达式访问者将有更多机会遇到堆栈溢出(是的,我们实际上在测试中就达到了))
稍后我们将这样的树发送给ADO.NET提供程序,该提供程序可以识别这种模式并将其减少为SQL生成期间的IN子句。
当我们增加对EF4中包含的Enumerable.Contains的支持时,我们认为这样做是很必要的,而不必在提供程序模型中引入对IN表达式的支持,说实话,10,000远远超过了我们预期客户将传递给的元素数量可枚举。就是说,我知道这很烦人,并且在特定情况下对表达式树的 *** 作使事情变得太昂贵了。
我与一位开发人员讨论了此问题,我们相信将来我们可以通过为IN添加一流的支持来更改实现。我将确保将其添加到我们的待办事项列表中,但是鉴于我们还有很多其他改进要做,因此我无法保证何时才能实现。
对于该线程中已经建议的解决方法,我将添加以下内容:
考虑创建一种方法来平衡数据库往返次数与传递给Contains的元素数量之间的平衡。例如,在我自己的测试中,我观察到针对SQL
Server的本地实例进行计算和执行时,具有100个元素的查询需要1/60秒的时间。如果您以这样的方式编写查询,即使用100个不同的ID集执行100个查询将为您提供与10,000个元素相同的查询结果,那么您可以在大约1.67秒而不是18秒的时间内得到结果。
根据查询和数据库连接的延迟,不同的块大小应更好地工作。对于某些查询,即,如果传递的序列重复,或者在嵌套条件下使用Enumerable.Contains,则可能会在结果中获取重复元素。
这是一个代码片段(很抱歉,如果用于将输入切成块的代码看起来太复杂了。实现相同功能的方法比较简单,但是我试图提出一种模式,该模式可以保留序列和我在LINQ中找不到类似的东西,所以我可能超出了这个部分:)):
用法:
var list = context.GetMainItems(ids).ToList();
上下文或存储库的方法:
public partial class ContainsTestEntities{ public IEnumerable<Main> GetMainItems(IEnumerable<int> ids, int chunkSize = 100) { foreach (var chunk in ids.Chunk(chunkSize)) { var q = this.MainItems.Where(a => chunk.Contains(a.Id)); foreach (var item in q) { yield return item; } } }}
分割可枚举序列的扩展方法:
public static class EnumerableSlicing{ private class Status { public bool EndOfSequence; } private static IEnumerable<T> TakeOnEnumerator<T>(IEnumerator<T> enumerator, int count, Status status) { while (--count > 0 && (enumerator.MoveNext() || !(status.EndOfSequence = true))) { yield return enumerator.Current; } } public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> items, int chunkSize) { if (chunkSize < 1) { throw new ArgumentException("Chunks should not be smaller than 1 element"); } var status = new Status { EndOfSequence = false }; using (var enumerator = items.GetEnumerator()) { while (!status.EndOfSequence) { yield return TakeonEnumerator(enumerator, chunkSize, status); } } }}
希望这可以帮助!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)