Partager via


EF Caching with Jarek Kowalski's Provider


The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.


Jarek Kowalski built a great caching and tracing toolkit that makes it easy to add these two features onto the Entity Framework. His code sample plugs in as a provider that wraps the original store provider you intend to use. This guide will help you get started using Jarek’s tools to enable caching and/or tracing in your application.

Caching can be especially valuable in an application that repeatedly retrieves the same data, such as in a web application that reads the list of products from a database on each page load. Tracing allows you to see what SQL queries are being generated by the Entity Framework and determine when these queries are being executed by writing them to the console or a log file. When these two features are used together, you can use the tracing output to determine how caching has changed your application’s pattern of database access.

Although the Entity Framework currently does not ship with caching or tracing “in the box,” you can add support for these features by following this walkthrough. These instructions cover only the essentials of enabling caching and tracing using Jarek’s provider samples. More advanced details about how to configure these samples are available in the resources Jarek provides online.

Below is a diagram that represents the change we will be making to the query execution pathway in order to plug in the caching and tracing features. The extensibility point we will be using is at the store provider level of the entity framework.

  In addition, below is a diagram that should elucidate why it is referred to as a “wrapping” provider and what it looks like when there is a cache hit versus a cache miss during query execution. As you can see below, the providers conceptually wrap around your existing provider.

 

Gathering Resources and Setting Up

Let’s get started.  Here are the new resources you will need to follow along with this walkthrough:

· Download “Tracing and Caching Provider Wrappers for Entity Framework” from MSDN to a folder where you keep your visual studio projects

· Download the extended context code generator, “ExtendedContext.vsix,” from the link at the bottom of this post

First, install the visual studio extension my colleague Matt Luebbert created that will automatically generate certain boilerplate code for you.  A vsix file like this is an installable visual studio extension that adds functionality to your IDE.  This particular vsix adds a T4 template that generates a class that extends your existing entity container so that it can work with the new caching and tracing features.  Note, however, that it also has dependencies on adding both of Jarek’s caching and tracing resources to your project.  This does not necessarily mean that to use caching you must use tracing, or vice versa, but you do have to provide your project with references to both resources in order to use this extra tool we have provided for convenience.  If you do not want to use this tool, you can also generate the necessary code by hand according to Jarek’s instructions here.

 

To install the extension, double-click the “ExtendedContext.vsix” file you downloaded to your desktop.  Then click “Install.”  

 

 

Click "Close" after the installation completes successfully.

Now open your project in Visual Studio. If you would like a sample project to get started, you can use the same one I am using, which I created in a previous walkthrough the Absolute Beginner’s Guide to Entity Framework. For this particular sample, I am using the same models as in that walkthrough but a simpler version of the executable code which also demonstrates the benefits of caching more obviously. Here is the code in the file Program.cs that I am starting with this time.

 

using System;

using System.Linq;

namespace ConsoleApplication2

{

    class Program

    {

        static void Main(string[] args)

        {

            using (Model1Container context = new Model1Container())

            {

                //Add an entity

                context.Users.AddObject(new User { Username = "User1", Password = "password" });

                context.SaveChanges();

                User findUser = context.Users.FirstOrDefault(user => user.Username == "User1");

                Console.WriteLine("Found user: " + findUser.Username);

                Console.WriteLine("User is attending " + findUser.ConfirmedEvents.Count() +

                    " events, including: ");

            }

            using (Model1Container newContext = new Model1Container())

            {

                User findUser = newContext.Users.FirstOrDefault(user => user.Username == "User1");

                Console.WriteLine("Found user: " + findUser.Username);

                Console.WriteLine("User is attending " + findUser.ConfirmedEvents.Count() +

                    " events, including: ");

            }

            Console.Read(); //Pause execution

        }

    }

}

 

 

 Now we need to add the proper references to Jarek’s code. To do this, from the File menu of Visual Studio click “Add” >> “Existing Project…”

 

First, add the project named EFProviderWrapperToolkit.csproj which is in the EFProviderWrapperToolkit folder of Jarek’s download. Now, add the reference to that project from the project in which we are enabling caching. To do this, right-click on “References” for your project in Solution Explorer, and then select “Add Reference…” Under the “Projects” tab select the EFProviderWrapperToolkit and click “OK” to add a reference for this code into your project.

 

The EFProviderWrapperToolkit is a common dependency for both caching and tracing, so regardless of whether you wish to use one or both of those features, this is a necessary inclusion.

Enabling Tracing

To add tracing functionality, follow the same steps as above to add the EFTracingProvider.csproj file which is located in the EFTracingProvider folder within the downloaded code.

 

 

After you have added the project, right-click on “References” in the Solution Explorer and click “Add Reference…” to add the necessary reference for the EFTracingProvider components.

If you would like to follow along with this walkthrough and use the Extended Context generator, which depends on also adding the EFCachingProvider code, repeat that same procedure once more to add the “EFCachingProvider” project, and then add a reference to that project as above.  This is required to satisfy a dependency of the class the Extended Context generator writes for you, since that class is designed to work with both caching and tracing and therefore depends on both.

Now we will make use of that convenient extended context generator that came in the VSIX file.  Right-click on the design surface of your project’s .edmx file and click “Add Code Generation Item.”  You should see “EF Caching and Tracing Content Code Generator” as an option.  Select that generator and click “Add.”  You will also need to come back to this screen to add the “ADO.NET Entity Object Generator,” but you can only add one code generator at a time.

 

 

 

 

You will likely see a security warning as below and perhaps a few other times while using this add-on code.  Click “OK” to proceed whenever this warning appears.

 

 

 

This will add the ExtendedContext file to your project, like this:

 

Here is an example of what this generator automatically writes for you. This is what is contained in the ExtendedContext1.cs file listed above.

  

//------------------------------------------------------------------------------

// <auto-generated>

// This code was generated from a template.

//

// Changes to this file may cause incorrect behavior and will be lost if

// the code is regenerated.

// </auto-generated>

//------------------------------------------------------------------------------

using System;

using System.IO;

using EFCachingProvider;

using EFCachingProvider.Caching;

using EFProviderWrapperToolkit;

using EFTracingProvider;

namespace ConsoleApplication2

{

    public partial class ExtendedModel1Container : Model1Container

    {

       private TextWriter logOutput;

   

        public ExtendedModel1Container()

            : this("name=Model1Container")

        {

        }

   

        public ExtendedModel1Container(string connectionString)

            : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(

                    connectionString,

                    "EFTracingProvider",

                    "EFCachingProvider"

            ))

        {

        }

   

        #region Tracing Extensions

   

        private EFTracingConnection TracingConnection

        {

            get { return this.UnwrapConnection<EFTracingConnection>(); }

        }

   

        public event EventHandler<CommandExecutionEventArgs> CommandExecuting

        {

            add { this.TracingConnection.CommandExecuting += value; }

            remove { this.TracingConnection.CommandExecuting -= value; }

        }

   

        public event EventHandler<CommandExecutionEventArgs> CommandFinished

        {

            add { this.TracingConnection.CommandFinished += value; }

            remove { this.TracingConnection.CommandFinished -= value; }

        }

   

        public event EventHandler<CommandExecutionEventArgs> CommandFailed

        {

            add { this.TracingConnection.CommandFailed += value; }

            remove { this.TracingConnection.CommandFailed -= value; }

        }

   

        private void AppendToLog(object sender, CommandExecutionEventArgs e)

        {

            if (this.logOutput != null)

            {

                this.logOutput.WriteLine(e.ToTraceString().TrimEnd());

                this.logOutput.WriteLine();

            }

        }

   

        public TextWriter Log

        {

            get { return this.logOutput; }

            set

            {

                if ((this.logOutput != null) != (value != null))

                {

                    if (value == null)

                    {

                        CommandExecuting -= AppendToLog;

                    }

                    else

                    {

                        CommandExecuting += AppendToLog;

                    }

                }

   

                this.logOutput = value;

            }

        }

   

    

        #endregion

   

        #region Caching Extensions

   

        private EFCachingConnection CachingConnection

        {

            get { return this.UnwrapConnection<EFCachingConnection>(); }

        }

   

        public ICache Cache

        {

            get { return CachingConnection.Cache; }

            set { CachingConnection.Cache = value; }

        }

   

        public CachingPolicy CachingPolicy

        {

            get { return CachingConnection.CachingPolicy; }

            set { CachingConnection.CachingPolicy = value; }

        }

   

        #endregion

   

    }

}

 

 

Repeat this same procedure for adding a code generation item by right-clicking the design surface and  adding the” ADO.NET Entity Object Generator,” a generator that comes as part of the Entity Framework.

Now add the necessary “using” directives at the top of the file(s) in which you’ll be using tracing and/or caching.  In other words, add them wherever you construct contexts and manipulate your entities.  If you are following along, add this to the top of the “Program.cs” file.  As with adding the projects and references, the EFProviderWrapperToolkit is required for either tracing or caching.  However, here you can add only the references for the features you intend to use.  For example, if you only want to use tracing, you could reference only EFProviderWrapperToolkit and EFTracingProvider to just get the resources for tracing.  If you only wanted to use caching, reference EFProviderWrapperToolkit and EFCachingProvider.   Since I plan to use both caching and tracing in this walkthrough, I will include all three references right now.

using EFProviderWrapperToolkit;

using EFTracingProvider;

using EFCachingProvider;

 

 

You also need to register the provider for whichever of the features you wish to use, which is done separately for each the EFTracingProvider and the EFCachingProvider.  This is the crucial step of plugging in to the extensibility point at the store provider level.  This can be accomplished very conveniently through the RegisterProvider method as demonstrated below.  For this walkthrough I will register both, but you could also register just the feature you plan to use.  This registration needs to be called only once during execution, so place this configuration code somewhere in your application that will be called only once and at the beginning of execution before you would like to use either caching or tracing. Note the difference between lines below – their identical length and subtle difference can be misleading. 

 

EFTracingProviderConfiguration.RegisterProvider(); //Add this line if you plan to use tracing

EFCachingProviderConfiguration.RegisterProvider(); //...This one is for caching

 

In the same section of your code where you placed the above provider registration, you can specify the configuration settings.  A more complete list of settings can be found online, but for now I will just turn on tracing to see that it is working.  Add this line right below the RegisterProvider lines above, potentially within an “if defined” block so you can toggle it on and off easily.  If you do use an “if defined” block, don’t forget to add “#define LOGGING” at the top of the file or at compilation time.

 

 

#if LOGGING

            EFTracingProviderConfiguration.LogToConsole = true; //Optional: for sending tracing to console

#endif

 

 

Finally, for any context in your application in which you would like to use caching or tracing, prepend “Extended” to the name of the context you construct. For example, my entity container was called “Model1Container” before installing these features, and the extended context code generator named the class it created as “ExtendedModel1Container.” You can automate this replacement by using find and replace. Select from the top menu bar: “Edit” >> “Find and Replace” >> “Quick Replace.”

 

Here is what my code looks like now (things I changed since the beginning of this walkthrough are highlighted in gray):

#define LOGGING

using System;

using System.Linq;

using EFProviderWrapperToolkit; //Required for caching and/or tracing

using EFTracingProvider; //Required for tracing

using EFCachingProvider; //Required for caching

namespace ConsoleApplication2

{

    class Program

    {

        static void Main(string[] args)

        {

            EFCachingProviderConfiguration.RegisterProvider();

            EFTracingProviderConfiguration.RegisterProvider();

#if LOGGING

            EFTracingProviderConfiguration.LogToConsole = true; //Optional: for sending tracing to console

#endif

            using (ExtendedModel1Container context = new ExtendedModel1Container())

            {

                //Add an entity

                context.Users.AddObject(new User { Username = "User1", Password = "password" });

             context.SaveChanges();

                User findUser = context.Users.FirstOrDefault(user => user.Username == "User1");

                Console.WriteLine("Found user: " + findUser.Username);

                Console.WriteLine("User is attending " + findUser.ConfirmedEvents.Count() +

                    " events, including: ");

            }

            using (ExtendedModel1Container newContext = new ExtendedModel1Container())

            {

                User findUser = newContext.Users.FirstOrDefault(user => user.Username == "User1");

                Console.WriteLine("Found user: " + findUser.Username);

                Console.WriteLine("User is attending " + findUser.ConfirmedEvents.Count() +

                    " events, including: ");

       }

            Console.Read(); //Pause execution

        }

    }

}

 

 

 

Now you are ready to test if tracing is working. Build and run your application. Whenever the database is queried, you should see output to the console like below. Each logging output consists of the following:

First line (white): The query counter, starting at 1 from the beginning of execution.
Next lines (gray): The actual query executed against the database
Last line (green): An indication that the query was complete and the amount of time it took.

 

 

Enabling Caching

Now that we have tracing working, let’s enable some the caching functionality. Begin by adding a new directive at the top of the file in which we added the other “using” statements.

using EFCachingProvider.Caching;

Then, define the cache (where the items will be stored) and the caching policy (which items will be stored). It makes the most sense to define these beyond the scope of any one context so that multiple contexts can use the same cache, perhaps directly below the registration and configuration lines added earlier. That ability to share the same cache among multiple contexts in your application is one of the primary benefits of a second-level cache.

For example, you could use a basic in-memory cache and a policy that caches all of your entities.

ICache IMCache = new InMemoryCache();

CachingPolicy cachingPolicy = CachingPolicy.CacheAll;

 

In the above code, ICache is a public interface for a cache store, created by Jarek for his toolkit, and InMemoryCache is a class he provides that implements that interface.

Lastly, you have to inform each context explicitly which cache it will use and how it will cache entities. You do this by telling the context about your cache as follows, referencing the cache and policy just created:

context.Cache = IMCache;

context.CachingPolicy = cachingPolicy;

Now the context will use the cache you defined. Here’s how my code looks with the new additions.

#define LOGGING

using System;

using System.Linq;

using EFProviderWrapperToolkit; //Required for caching and/or tracing

using EFTracingProvider; //Required for tracing

using EFCachingProvider; //Required for caching

using EFCachingProvider.Caching; //Required for using the InMemoryCache

namespace ConsoleApplication2

{

    class Program

    {

        static void Main(string[] args)

        {

            EFCachingProviderConfiguration.RegisterProvider();

            EFTracingProviderConfiguration.RegisterProvider();

#if LOGGING

            EFTracingProviderConfiguration.LogToConsole = true; //Optional: for sending tracing to console

#endif

            ICache IMCache = new InMemoryCache();

            CachingPolicy cachingPolicy = CachingPolicy.CacheAll;

            using (ExtendedModel1Container context = new ExtendedModel1Container())

            {

                context.Cache = IMCache; //Required at the beginning of the context

                context.CachingPolicy = cachingPolicy; //Also required at the beginning of the context

                //Add an entity

                context.Users.AddObject(new User { Username = "User1", Password = "password" });

                context.SaveChanges();

                User findUser = context.Users.FirstOrDefault(user => user.Username == "User1");

                Console.WriteLine("Found user: " + findUser.Username);

                Console.WriteLine("User is attending " + findUser.ConfirmedEvents.Count() +

                    " events, including: ");

            }

            using (ExtendedModel1Container newContext = new ExtendedModel1Container())

            {

                newContext.Cache = IMCache; //Required at beginning of every context - references ICache from above

                newContext.CachingPolicy = cachingPolicy; //Also required for every cache - references CachingPolicy from above

                User findUser = newContext.Users.FirstOrDefault(user => user.Username == "User1");

                Console.WriteLine("Found user: " + findUser.Username);

                Console.WriteLine("User is attending " + findUser.ConfirmedEvents.Count() +

                    " events, including: ");

          }

            Console.Read(); //Pause execution

        }

    }

}

 

Build and run your program again to see what changes. You should be able to see some difference in the number of database calls, likely fewer than before when only tracing was enabled. You might not see any changes in the number of calls if you primarily write to the database or read items only once during execution, since the cache is only useful during repetitive reads. However, in applications with redundant reads from the database, such as this sample I have been constructing, caching can provide some great performance and load benefits. The table below shows a side-by-side comparison that visually demonstrates the result of caching. Notice how when using caching each query is sent to the database only once.

 

 

 

Voila! That’s basic caching at work.

If you would like to learn more about this toolkit, you can visit Jarek’s blog and read through the blog post where he describes caching in more advanced detail. And if you have any feedback regarding the provider, you can reply in the comments below or reach out to Jarek via his blog contact form.

 

ExtendedContext.vsix.txt

Comments

  • Anonymous
    September 13, 2010
    Is there any way to use this with CodeOnly DbContext?

  • Anonymous
    September 14, 2010
    Also, some of the files that it seems to rely on don't exist with the CodeOnly model, so it doesn't work even with a normal ObjectContext from that standpoint.

  • Anonymous
    September 17, 2010
    Hi, What if I modify my database somewhere else than in my web app where I use the caching part (aspnetcache)? Do the  EFCachingProvider will refresh his data? If no, how to explicitly said that we have to reset the cache for this element? Thanks :)

  • Anonymous
    September 29, 2010
    I'd also like to find out the answer to Derek's question...  will it be possible to use this with Code-Only... if not during the CTP at least when  the release hits? It'd be great to see an example of how this would work, also.

  • Anonymous
    October 10, 2010
    Hi, I use IoC for EF ObjectContexts (ObjectContexts live in HttpContext). Is the above provider works fine for IoC? Thanks.

  • Anonymous
    October 10, 2010
    Hi, I use IoC for EF ObjectContexts (ObjectContexts live in HttpContext). Does the above provider work fine for IoC? Thanks.

  • Anonymous
    November 01, 2010
    Does this provider work with EF v1. I am currently using Entity Framework v1 on the project and the company doesn't plan to migrate anytime soon.

  • Anonymous
    November 08, 2010
    Hi I have the following exception in EntityConnectionWrapper utility:   // load item collections from XML readers created from XElements...            EdmItemCollection eic = new EdmItemCollection(csdl.Select(c => c.CreateReader()));     error-->       StoreItemCollection sic = new StoreItemCollection(ssdl.Select(c => c.CreateReader()));            StorageMappingItemCollection smic = new StorageMappingItemCollection(eic, sic, msl.Select(c => c.CreateReader())); The exception is : Schema specified is not valid. Errors: (0,0) : error 0175: The specified store provider cannot be found in the configuration, or is not valid. I do not understand how to fix this as the DLLs are in the running dir Thnak Jonathan

  • Anonymous
    November 17, 2010
    @Jonathan, don't forget to call the base ObjectContext ctor with the DefaultContainerName as second argument

  • Anonymous
    November 17, 2010
    @Jonathan, sorry I think your error is for not calling the RegisterProvider method on EFCachingProviderConfiguration/EFTracingProviderConfiguration.

  • Anonymous
    November 20, 2010
    Any chance of something like this getting rolled in to the framework itself and officially supported? :)

  • Anonymous
    December 31, 2010
    Hi, Is this mechanism compaible with STE ? How can I add this T4 template for caching and tracing plus STE T4 template for a model and gain both features of Self-Tracking and these together ? Thanks

  • Anonymous
    January 05, 2011
    Any word on stored proc caching ever  been supported ? given that our LOB application has many 100s of stored procs it's a show stopper to using entity framework. Thanks.

  • Anonymous
    February 10, 2011
    How does this solution support manual invalidation of data? (using AppFabric Caching in Azure)

  • Anonymous
    May 15, 2011
    I'm getting following exception on the client while using both tracing and caching providers:      <p>The server encountered an error processing the request. The exception message is 'The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.'. See server logs for more details. The exception stack trace is: </p>      <p>   at System.Data.Objects.ObjectContext.get_Connection()   at System.Data.Services.Caching.MetadataCache.TryLookup(Type serviceType, Object dataContextInstance)   at System.Data.Services.DataService1.CreateProvider() &nbsp; at System.Data.Services.DataService1.HandleRequest()   at System.Data.Services.DataService`1.ProcessRequestForMessage(Stream messageBody)   at SyncInvokeProcessRequestForMessage(Object , Object[] , Object[] )   at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)   at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)</p>

  • Anonymous
    September 02, 2013
    How to use EFCaching Provider with Entity Framework 6

  • Anonymous
    September 04, 2013
    @LP - We haven't looked at making the caching provider work with EF6 at this stage (the provider is written and maintained by a former member of the EF team). One of our team members is planning to look at it, but it won't be until we've finished working on the RTM of EF6. One of the main things would be to update the code to work against the moved provider model in EF6. If you wanted to try, there are details on upgrading providers here - entityframework.codeplex.com/wikipage.