DbSet.Find方法在按ID查找时比.SingleOrDefault方法慢得离谱

38

我有以下代码(数据库是 SQL Server Compact 4.0):

Dim competitor=context.Competitors.Find(id)

当我进行性能分析时,从仅有60条记录的表中检索竞争对手的Find方法需要300多毫秒。

当我更改代码为:

Dim competitor=context.Competitors.SingleOrDefault(function(c) c.ID=id)

然后在仅3毫秒内找到了竞争者。

竞争者类:

Public Class Competitor
    Implements IEquatable(Of Competitor)

    Public Sub New()
        CompetitionSubscriptions = New List(Of CompetitionSubscription)
        OpponentMeetings = New List(Of Meeting)
        GUID = GUID.NewGuid
    End Sub

    Public Sub New(name As String)
        Me.New()
        Me.Name = name
    End Sub

    'ID'
    Public Property ID As Long
    Public Property GUID As Guid

    'NATIVE PROPERTIES'
    Public Property Name As String

    'NAVIGATION PROPERTIES'
    Public Overridable Property CompetitionSubscriptions As ICollection(Of CompetitionSubscription)
    Public Overridable Property OpponentMeetings As ICollection(Of Meeting)
End Class

我使用流畅的API为CompetitionSubscriptionsOpponentMeetings定义了多对多关系。

Competitor类的ID属性是Long类型,Code First将其转换为Identity列,并作为主键存储在数据表中(SQL Server Compact 4.0)

这里发生了什么??


“Find” 方法首先检查内部上下文存储以返回现有实例,而无需往返数据库。您的上下文中加载了多少条记录? - Ladislav Mrnka
@LadislavMrnka 我不知道有多少记录被加载。当我调用这段代码时,明确地很少。我该如何检查这个?而且只有60个“Competitor”记录! - Dabblernl
1个回答

60

Find 方法内部调用 DetectChanges,而 SingleOrDefault(或者通常的任何查询)不会调用 DetectChanges。由于 DetectChanges 是一个耗费性能的操作,这就是为什么 Find 比较慢的原因(但如果实体已经加载到上下文中,Find 将不会运行查询而直接返回已加载的实体,因此可能会变得更快)。

如果你想要在循环中使用 Find 查找大量实体,可以像下面这样禁用自动更改检测(以下是 C# 的示例代码):

try
{
    context.Configuration.AutoDetectChangesEnabled = false;
    foreach (var id in someIdCollection)
    {
        var competitor = context.Competitors.Find(id);
        // ...
    }
}
finally
{
    context.Configuration.AutoDetectChangesEnabled = true;
}

现在,Find 不会在每次调用时调用 DetectChanges,并且它应该像 SingleOrDefault 一样快(如果实体已附加到上下文,则更快)。

自动更改检测是一个复杂且有些神秘的主题。这篇四部分系列文章提供了很好的详细讨论:

(第1部分链接,第2、3和4部分的链接位于该文章开头)

http://blog.oneunicorn.com/2012/03/10/secrets-of-detectchanges-part-1-what-does-detectchanges-do/


6
这个页面仍然被很多人找到,并且对答案点赞。但是需要注意的是,到目前为止(当前使用EF6),AutoDetectChangesEnabledFind()中现在对性能没有明显的影响,而且Find()的表现与Where().SingleOrDefault()相当,甚至略微更好一些。 - Jon Bellamy
1
@JonBellamy:有趣!这是你自己的测量结果还是在其他参考/文档中发现的(或两者都有)?根据我的理解,你所说的意思是Find不再调用DetectChanges或者DetectChanges的性能在EF 6中得到了显著提高,这将对EF的整体性能产生重大影响。 - Slauma
2
@JonBellamy:啊,好的,那么结果也许并不令人惊讶,因为如果上下文包含“许多”对象,则DetectChanges会变得很慢。如果上下文中只有一个对象或甚至是一个空上下文,那么开销可能可以忽略不计。 - Slauma
8
我刚刚下载了 EF 6.0.2 的源代码,并发现在我的情况下,DbSet<T>.Find 方法仍然调用 DetectChanges 并且执行时间比 FirstOrDefault 长得多(+60%)。你可以在这里找到源代码:https://dev59.com/pnvaa4cB1Zd3GeqPJexK - Adolfo Perez
3
关于这个已经被修复的评论:不!我正在使用 EF 6.1,但 Find() 对我来说非常慢,而使用 SingleOrDefault(x => x.Id == "foo") 则非常迅速。 - xanadont
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接