Entity Framework 5.0
Code First building blocks
There are plenty of examples out there showing how to use DbContext to create a Code First model. But DbContext itself uses some fundamental Code First building blocks to do its stuff. This post shows how these building blocks can be used directly, which can be useful in situations where you need more control over how the model is created or cached.
When would you do this?
DbContext can be used in the normal way for most applications. However, there are currently two areas for which we get quite a few questions where the best approach is to fall back to using the lower-level building blocks.The first area is caching of the model. DbContext caches the model based on the type of your context. For example, if you have a context class called BlogContext, then DbContext will build the model once the first time it is used and for subsequent uses the cached model will be used. This doesn't work if you have one context type but want to use it with different models.
The second area is direct control over the type of database that the model will target. DbContext creates a connection to the database to determine appropriate mappings. For example, if the database is SQL Server 2008, then mappings for types only available in SQL Server 2008 might be used. If you need the model to also work with SQL Server 2005, then you'll need to tell Code First explicitly to do this.
It's a river rock thing…
The paragraphs above describe concrete situations in which in makes sense to use the Code First building blocks. However, the main reason the building blocks are there is to account for situations we haven't anticipated. The building blocks provide flexibility to Code First allowing it to be used in new and interesting ways.Let's do it!
Let's take this very simple model and context as an example:public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
Normally you would use the context like this:
using (var context = new BlogContext())
{
context.Blogs.Add(new Blog { Name = "One Unicorn" });
context.SaveChanges();
}
When the context is used for the first time (in the Add call above) it will normally go through the process of building the model. Let's instead see how this can be done using the lower-level building blocks.
It's all about DbModelBuilder
Building a Code First model always starts with an instance of DbModelBuilder. You've probably seen DbModelBuilder in the OnModelCreating method. This is where DbContext gives you access to the model builder it is using to create a model. To use the lower-level building blocks you create the DbModelBuilder directly:var builder = new DbModelBuilder();
Once you have a model builder you need to give it some starting points to discover the model. This is done by calling the Entity method on the model builder:
builder.Entity<Post>();
builder.Entity<Blog>();
DbContext normally does this for you by looking at the DbSets you have declared on your context and calling the Entity method for each of these.
Some other things to note here:
- You don't need to call Entity for every entity type in your model. Code First follows references in the types it is given and so will discover all the model that is reachable from the types you give it. For example, in the code above calling Entity for only Blog or only Post would result in the same model because Post is reachable from Blog and vice versa.
- You can also use the ComplexType method to tell the builder about complex types.
- You can create EntityTypeConfiguration and ComplexTypeConfiguration instances and add them to the builder instead of calling the Entity/ComplexType methods.
- You can provide additional configuration starting from each Entity call using the fluent API just as you would when overriding OnModelCreating
Building the model
Once you have the model builder configured you need to build the model. This is where you have more options using the building blocks. DbContext normally builds the model in this way:var model = builder.Build(connection);
As described above, this uses the given connection to connect to the database and determine appropriate mappings. If, for example, you want to explicitly tell Code First to build a database for SQL Server 2005, then you would instead build the model in this way:
var model = builder.Build(
new DbProviderInfo("System.Data.SqlClient", "2005"));
The DbModel instance returned by the Build method is an object model representation of the underlying Entity Data Model (EDM). Unfortunately as of EF5 this EDM object model is not exposed publicly, so the DbModel instance is opaque. We intend to make this object model public in EF6, but as with anything that hasn't happened yet, that could change. Once it is public you will be able to manipulate the EDM directly to tweak the model created by Code First.
Compiling the model
The EDM object model isn't used directly by DbContext while the app runs, so it first needs to be compiled into a form that can be used. To do this call the Compile method:var compiledModel = model.Compile();
For EF geeks, the DbCompiledModel returned is currently a thin wrapper around a MetadataWorkspace, but this may not always be the case.
Caching the compiled model
The DbCompiledModel instance is the thing that DbContext caches. When using these building blocks you should find a way to cache the compiled model in your app. Likewise, do your own caching of the compiled model instances in situations where you need to use multiple models with the same context type.Using the compiled model
Once you have a compiled model it's easy to use it with DbContext—just pass an instance to the appropriate constructor. You'll need to modify your context class to allow this:public class BlogContext : DbContext
{
public BlogContext(DbCompiledModel model)
: base(model)
{
}
public DbSet<Blog> Blogs { get; set; }
public DbSet<Posts> Posts { get; set; }
}
And then call it where you use the context:
using (var context = new BlogContext(compiledModel))
{
context.Blogs.Add(new Blog { Name = "One Unicorn" });
context.SaveChanges();
}
DbContext will then use the given model instead of going through the process of creating one itself.