我在使用EF6的LINQ查询时遇到了编译缓慢的问题。我知道EF会为LINQ查询缓存已编译的查询计划,但是有些地方需要注意(例如Enumerable.Contains会防止缓存)。为了调试目的,我想查看缓存以验证我的查询是否得到了正确的缓存。我该如何做呢?
注意:由于这仅用于调试,因此如果使用反射或其他不会在生产中使用的方法进行回答,我会很高兴。
我在使用EF6的LINQ查询时遇到了编译缓慢的问题。我知道EF会为LINQ查询缓存已编译的查询计划,但是有些地方需要注意(例如Enumerable.Contains会防止缓存)。为了调试目的,我想查看缓存以验证我的查询是否得到了正确的缓存。我该如何做呢?
注意:由于这仅用于调试,因此如果使用反射或其他不会在生产中使用的方法进行回答,我会很高兴。
您可以使用类似以下代码(在 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所需的时间。如果您的查询正在被缓存,那么在第一次执行查询之后,该时间将缩短到几乎为零;如果没有缓存,则该时间将始终保持高水平。您可以注入一个IDbCommandTreeInterceptor
实现,用于记录任何查询树的创建。我曾成功地将其与调用堆栈键控字典结合使用,以便发现“坏”查询的多次编译。