How can I view the Entity Framework LINQ query plan cache?

caching entity-framework entity-framework-6 linq

Question

I'm having issues with slow LINQ query compilation in EF6. I am aware that EF stores built query plans for LINQ queries in a cache, yet there are several catch-22 situations (such as Enumerable. Prevents caching (contains). In order to verify that I'm receiving proper caching for my queries, I'd like to access the cache for debugging purposes. How can I go about this?

Note that because this is only for debugging, I'd be satisfied with a solution that uses reflection or another technique that wouldn't be employed in a real-world setting.

1
4
1/11/2016 6:31:46 PM

Popular Answer

In EF 6.1.3, you can reflect all the way down to the cache by doing something like this:

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());
}

Unfortunately, all of the cache's items are ofinternal (In the) kindsSystem.Data.Entity.Core.Common.QueryCache namespace), therefore extracting information from them will demand a lot more thought and investigation. Luckily,CompiledQueryCacheKey overrides ToString Consequently, it divulges some (cryptic) information about itself. After executing just one query (Table.Count() ), the code above generates the following two entries:

[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]

When you have a lot of entries, good luck figuring out what that implies or connecting the dots.

Making a class with a method I can assign to it is another strategy I've tried and had some luck with.DbContext.Database.Log in the testing. When the message "Opened connection..." appears, the class starts a timer, which it then stops when the message "— Executing at..." appears. That period of time roughly reflects the time required by EF to translate your LINQ query into SQL. After the initial execution of your query, that time should practically disappear if it is being cached; otherwise, it will remain consistently high.

You would only want to perform this in a testing situation, it should go without saying.

4
1/11/2016 9:38:32 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow