为什么Contains()运算符会如此大幅度降低Entity Framework的性能?

为什么Contains()运算符会如此大幅度降低Entity Framework的性能?,第1张

为什么Contains()运算符会如此大幅度降低Entity Framework的性能?

更新:通过在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); }        }    }}

希望这可以帮助!



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5440882.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-11
下一篇 2022-12-11

发表评论

登录后才能评论

评论列表(0条)

保存