So you are using entity framework and frustrated about slow application startup? In this blog post I will show you three steps for massively improved first query performance (80% decrease), especially for code-first scenarios. With these steps, first query-time in our application dropped from ~4.5 seconds down to ~1 second!
- Autor
- David Roth
- Datum
- 3. Januar 2015
- Lesedauer
- 3 Minuten
1. Using a cached db model store
This has probabily the biggest effect on startup performance and is only necessary if you are using the code first model. Building and compiling large models using the Code First pipeline is extremly expensive in terms of start up time. This step will cache the code-first pipeline with its expensive o-c mapping generation and will store it in a xml file on the filesystem. The next time your application starts, EF will deserialize this cached mapping file which significantly reduces startup time.
You can enable the cached db model store with the following lines of code:
public class MyContextConfiguration : DbConfiguration
{
public MyContextConfiguration()
{
if (useCachedDbModelStore)
{
string cachePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\YOUR_APP_NAME\EFCache\";
MyDbModelStore cachedDbModelStore = new MyDbModelStore(cachePath);
IDbDependencyResolver dependencyResolver = new SingletonDependencyResolver<DbModelStore>(cachedDbModelStore);
}
}
private static bool useCachedDbModelStore;
// Note that you should only enable DbContextStore during normal run scenarios without migrations. Migrations are currently not supported and will crash.
public static void Configure(bool useCachedDbModelStore)
{
MyContextConfiguration.useCachedDbModelStore = useCachedDbModelStore;
}
private class MyDbModelStore : DefaultDbModelStore
{
public MyDbModelStore(string location)
: base(location)
{ }
public override DbCompiledModel TryLoad(Type contextType)
{
string path = GetFilePath(contextType);
if (File.Exists(path))
{
DateTime lastWriteTime = File.GetLastWriteTimeUtc(path);
DateTime lastWriteTimeDomainAssembly = File.GetLastWriteTimeUtc(typeof(TypeInYourDomainAssembly).Assembly.Location);
if (lastWriteTimeDomainAssembly > lastWriteTime)
{
File.Delete(path);
Tracers.EntityFramework.TraceInformation("Cached db model obsolete. Re-creating cached db model edmx.");
}
}
else
{
Tracers.EntityFramework.TraceInformation("No cached db model found. Creating cached db model edmx.");
}
return base.TryLoad(contextType);
}
}
}
Using the cached db model store saves ~3.5 seconds on my I7 developer machine when using a model with about 80 entities.
Important: Since the model store has to be invalidated and rebuild every time your model changes, this method will only make sense if your entities are located within an isolated assembly. Otherwise the cache will be invalidated every time you change a line in your code and so you wont be saving anything.
Note that you should only enable DbContextStore during normal run scenarios without migrations. Migrations are currently not supported and will crash.
2. Generate pre-compiled views
Before Entity Framework can execute a query or save changes to a data source, it must generate a set of local query views to access the database. These views are part of the metadata which is cached per application domain. Depending on the model size (amount of entities, associations etc.) view generation can have a significant enhancement on startup performance (1-30 seconds and more) and so caching this step is a must in order to achieve good EF startup performance.
There are different ways to generate and store pre-compiled views, however in my opionion the easiest and most flexible way is to use this very nice nuget package: Interactive Pregenerated Views for Entity Framework 6.
Once you have downloaded and installed the libary, you just need to configure it in your db context:
// Enable DbModelStore before creating your first DbContext.
// Do not call this method when using migrations, as they are currently not supported.
MyContextConfiguration.Configure(useCachedDbModelStore: true);
using (var ctx = new MyContext())
{
InteractiveViews
.SetViewCacheFactory(
ctx,
new FileViewCacheFactory(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\YOUR_APP_NAME\EFCache\"));
Now you only pay the view generation penalty the first time you start your application. All subsequent startups will use the cached xml file and will result in significant reduced startup time.
3. Generate pre-compiled version of entityframework using n-gen to avoid jitting
Entity Framework does not come in the default installation of the .net Framework. Therefore, the EF assembly is not NGEN’d by default which means that EF code needs to be JITTED each time the application starts. Since EF is a really large and complex framework (EntityFramework assembly has over 5MB), and most of the code paths are needed even for simple scenarios, Jitting has a noticeable degradation on startup performance.
Some rough benchmarks on my I7 developer machine and Core2 notebook showed a drop in startup time by about 1-2 seconds.
On really slow machines the performance gains can even be bigger (E.x. we measured 3-4 seconds JIT time on a slow virtualized windows server instance). Running NGEN against EF is as simple as executing the following command within a root terminal session:
%WINDIR%\Microsoft.NET\Framework\v4.0.30319\ngen install EntityFramework.dll
For more information about Entity Framework 6 and NGEN I recommend this msdn article: http://msdn.microsoft.com/en-us/data/dn582034.
If you have further questions on this topic you can ping me on twitter: rothdave.