One thing about Entity Framework that bugs me are how database initializers work. Database Initializers are used to initialize the database and determine the behavior of how the Entity Framework model interacts with the database. The default EF behavior compares the model to any meta data in the database and if it's not valid, throws a failed meta data exception. Initializers serve a valid purpose, but I tend to forget exactly what object I have to call to initialize them and what class I have to override to create them. Worst of all though, because database initializers are supposed to run only once per AppDomain they have to be placed in a very specific location in a project to fire at application startup. In other words, it's fairly hard to internalize database initializers.
How Database Initializers work
Database initializers initialize the database and are intended to run exactly once per AppDomain. This means they need to be run on application startup - either in a desktop apps startup code or in Application_Start in Web application.
For example to use an initializer in ASP.NET, you use syntax like the following:
protected void Application_Start()
{
Database.SetInitializer<ClassifiedsContext>(new DropCreateDatabaseIfModelChanges());
}
It's unlikely that you'll use one of the default database initializers in a live application, since they are pretty destructive but they can be useful for testing or during development when the database changes a lot. The more common scenario like is to use the Migration initializer:
Database.SetInitializer<ClassifiedsContext>(
new MigrateDatabaseToLatestVersion<ClassifiedsContext,MigrationConfiguration>());
Uff, that's a mouthful, eh? I tend to subclass this mouthful with my own class to make the initializer a little more approachable.
public class ClassifiedsModelMigrateInitializer :
MigrateDatabaseToLatestVersion<ClassifiedsContext,ClassifiedsBusiness.Migrations.MigrationConfiguration>
{
}
Initializers can override the Initialize() and Seed methods(). The Initialize method is responsible for checking the context and perform any additional initialization you might have to do. The default implementation checks the metadata in the database (if it exists) against the model. If you don't want that to happen you can simply implement an empty initializer like so:
public class QueueMessageManagerContextInitializer : IDatabaseInitializer<QueueMessageManagerContext>
{
protected void Seed(QueueMessageManagerContext context)
{
}
public void InitializeDatabase(QueueMessageManagerContext context)
{
// do nothing
Seed(context);
}
}
which can then be used like this:
Database.SetInitializer<QueueMessageManagerContext>(new QueueMessageManagerContextInitializer());
Turns out there's actually an even simpler way to have a non-actionable database Initializer - simply pass null:
Database.SetInitializer<QueueMessageManagerContext>(null);
My Problem
Earlier today I ran into just this problem. I have a single database with two DbContexts connected to it. The first is a very rich model that uses migrations and so uses the Db's meta data for validation and triggering migrations as needed. The second is a small component with a tiny model of two tables that is intended to just work without checking the existing metadata in the database. The second context tests out fine when running in its own database with hand created tables to map to, but if dumping those same tables to the live database that is also accessed by the first context it fails.
The above 'empty' initialization strategies work well to allow me to bypass the model meta data validation on startup.
But there's another problem here: The second context is part of a small reusable component that's meant to be generic. Requiring a custom database initializer is a pain for this because the initializer forces the consumer of my component to externally create the initializer and call it. IOW, the consumer has to remember to set up the initializer and place it in the right place in the startup sequence.
That sucks!
Internalizing the Database Initializer
Incidentally this is something that's bugged me a quite a bit about EF in other places. I always forget exactly what I need for implementing a database initializer in an app. How does it relate to the project what do I need to instantiate and where's the best place to put it etc. etc. It's ugly and not very discoverable. Frankly the only way I remember is to open up another project and see how I did it previous :-)
It seems to me that the initializer invocation is not an application responsibility but the responsibility of the context and that's where I would expect that behavior to live.
So I got to thinking - wouldn't it be nice to actually make the initialization more generic so that it can be called from anywhere and still be guaranteed to always fire just once.
Here's a DbContext utility method that does just that:
public static class DbContextUtils<TContext>
where TContext : DbContext
{
static object _InitializeLock = new object();
static bool _InitializeLoaded = false;
/// <summary>
/// Method to allow running a DatabaseInitializer exactly once
/// </summary>
/// <param name="initializer">A Database Initializer to run</param>
public static void SetInitializer(IDatabaseInitializer<TContext> initializer = null)
{
if (_InitializeLoaded)
return;
// watch race condition
lock (_InitializeLock)
{
// are we sure?
if (_InitializeLoaded)
return;
_InitializeLoaded = true;
// force Initializer to load only once
System.Data.Entity.Database.SetInitializer<TContext>(initializer);
}
}
}
Nothing fancy here - all this code really does is check to see if this code was previously by using a static flag to hold the state. If you recall, static properties/fields are global to the AppDomain so this SetInitializer call fires exactly once per AppDomain. This code needs to be called before the first full context invocation.
If the goal is to internalize this code as part of the context, it's easy to stick it into the constructor of the DbContext subclass you create. Here's an example:
public class QueueMessageManagerContext : DbContext
{
public QueueMessageManagerContext()
{
// don't validate the schema
DbContextExtensions<QueueMessageManagerContext>.SetInitializer(null);
}
public DbSet<QueueMessageItem> QueueMessageItems { get; set; }
}
Now you don't have to worry about when the initializer is called because the first access to your context automatically initializes the context using the specified initializer. This also keeps all behavior relative to the Context in one place, so personally I like this. You can also still use app startup code to call the method directly just like calling SetInitializer directly.
This is a small thing of course, but it's something that's important to me as in my current app I'm working on with a client we have many small self contained components that have micro EF models. I can now easily force all of those components to non check the meta data when they start and all can share the same database easily.
Resources:
Other Posts you might also like