我该如何查看Entity Framework LINQ查询计划缓存?

6

我在使用EF6的LINQ查询时遇到了编译缓慢的问题。我知道EF会为LINQ查询缓存已编译的查询计划,但是有些地方需要注意(例如Enumerable.Contains会防止缓存)。为了调试目的,我想查看缓存以验证我的查询是否得到了正确的缓存。我该如何做呢?

注意:由于这仅用于调试,因此如果使用反射或其他不会在生产中使用的方法进行回答,我会很高兴。


你的意思是SQL Server缓存查询计划,而不是EF? - Magnus
4
@Magnus EF缓存LINQ到SQL的翻译。SqlServer缓存将SQL翻译成执行查询的“查询计划”。我对EF正在做什么很感兴趣。 - ChaseMedallion
好的,所以你对查询本身感兴趣,而不是查询计划。 - Magnus
2个回答

5

您可以使用类似以下代码(在 EF 6.1.3 中)将反射到缓存:

var method = context.Database.GetType().GetMethod("CreateStoreItemCollection", BindingFlags.Instance | BindingFlags.NonPublic);
var storeItemsCollection = method.Invoke(context.Database, null);
var queryCacheManagerField = storeItemsCollection.GetType().GetField("_queryCacheManager", BindingFlags.Instance | BindingFlags.NonPublic);
var queryCacheManager = queryCacheManagerField.GetValue(storeItemsCollection);
var cacheField = queryCacheManager.GetType().GetField("_cacheData", BindingFlags.Instance | BindingFlags.NonPublic);
var cacheData = cacheField.GetValue(queryCacheManager) as ICollection;
foreach (var item in cacheData)
{
    Console.WriteLine(item.ToString());
}

很遗憾,缓存中的所有项目都是internal类型(位于System.Data.Entity.Core.Common.QueryCache命名空间中),因此要从中获取有用的信息需要更多的反射和探索。幸运的是,CompiledQueryCacheKey覆盖了ToString方法,因此它会提供一些(加密的)关于自身的信息。在运行单个查询(Table.Count())之后,上述代码将输出这两个条目:

[System.Data.Entity.Core.Common.QueryCache.ShaperFactoryQueryCacheKey`1[System.Int32], System.Data.Entity.Core.Common.QueryCache.QueryCacheEntry]

[FUNC<Edm.Count(In Transient.collection[Edm.Int32(Nullable=True,DefaultValue=)](Nullable=True,DefaultValue=))>:ARGS(([Project](BV'LQ1'=([Scan](DashboardAutoContext.Organizations:Transient.collection[DashboardAuto.Organization(Nullable=True,DefaultValue=)]))(1:Edm.Int32(Nullable=True,DefaultValue=)))))|||AppendOnly|True, System.Data.Entity.Core.Common.QueryCache.QueryCacheEntry]

祝你好运,尝试理解这意味着什么,或在有许多条目时进行相关性的对比。
我采用了不同的策略,并取得了一些成功。我创建了一个类,其中包含一个方法,可以在测试期间将其分配给DbContext.Database.Log。当该类看到“打开连接…”消息时,它会启动计时器,然后在看到“--执行于…”时停止计时器。该时间大致对应于EF将您的LINQ查询编译为SQL所需的时间。如果您的查询正在被缓存,那么在第一次执行查询之后,该时间将缩短到几乎为零;如果没有缓存,则该时间将始终保持高水平。
(毋庸置疑,您只需要在测试环境下执行此操作。)

2

您可以注入一个IDbCommandTreeInterceptor实现,用于记录任何查询树的创建。我曾成功地将其与调用堆栈键控字典结合使用,以便发现“坏”查询的多次编译。


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