Building A Full Stack Web Application – Part 3: Data Context

This post is a continuation in a series about building a full stack web application.  Previous posts in the series:

Microsoft’s Entity Framework (EF) is built around the concept of a data context, which is essentially a bridge between a database and classes, allowing us to interact with data as objects.  The EF class DbConext allows us to query and persist data in an object-oriented way, without having to deal with yucky SQL statements, database connection strings or transactions.

Context 

Back to Stocks Tracker.  Now that the data was modeled I added another class library project – Data.Context – which would be responsible for generating the database tables, keys and indexes, as well as seeding the database with any reference data.  This project provides a custom abstraction of DbContext, available to other layers.

But wait.  Remember how in a previous post, I showed how Microsoft’s Identity framework can be used to generate security classes that work hand-in-hand with Identity?  Well Identity also provides its own implementation of DbContext, IdentityDbContext, which allows us to interact with security data as objects.  Since I want to be able to query security objects via EF, the Stocks Tracker context will actually be derived from IdentityDbContext.

Let’s look a little closer at the context Stocks Tracker exposes to its callers.


/// <summary>
/// Class provides access to Entity Framework CRUD actions within the Stocks Tracker domain.
/// </summary>
public class StocksTrackerContext : IdentityDbContext<ApplicationUser>, IStocksTrackerContext

As you can see, StocksTrackerContext implements IdentityDbContext, which will use the ApplicationUser class defined in the Data.Models project. StocksTrackerContext also implements an interface, so that callers are limited to the data I want them to see.

public StocksTrackerContext()
: base("name=StocksTrackerContext")
{
}

public StocksTrackerContext(string connectionString, bool eagerOpen)
: base(connectionString)
{
_eagerOpen = eagerOpen;
if (eagerOpen)
Database.Connection.Open();
}

The constructors for DbContext are interesting.  Passing “name=StocksTrackerContext” forces EF to search the project for a connectionStrings node in a configuration file, and attempts to use the connection string matching the name to connect to the database.  The context can throw errors when called from a different project without a configuration file, so rather than duplicate config values all over the place, I prefer the second constructor, where an explicit connection string can be provided.


public IDbSet<Stock> Stocks
{
get { return _stocks ?? (_stocks = Set<Stock>()); }
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new StockConfiguration());
modelBuilder.Configurations.Add(new StockTrackerConfiguration());
modelBuilder.Configurations.Add(new StockTrackerStockConfiguration());
base.OnModelCreating(modelBuilder);
}

EF provides its own implementation of IEnumerable in the generic IDbSet interface.  This provides LINQ functionality on the collection, but with the ability to add and remove objects from the underlying collection.

But first we need a database.  EF provides a ton of configuration options for dealing with data, but I like to keep it simple.  Basically it boils down to configuring your entities, and migrating your data.  For configuration, EF provides a generic base class EntityTypeConfiguration, which for a model allows you to set constraints on tables (keys, name) and columns (size, nullability).

Configuration

/// <summary>
/// Defines the entity type configuration for the <see cref="Stock"/> domain model.
/// </summary>
public class StockConfiguration : EntityTypeConfiguration<Stock>
{
/// <summary&amp;gt;
/// Instantiates the StockConfiguration class.
/// </summary&amp;gt;
public StockConfiguration()
{
HasKey(s => s.StockId);

Property(s => s.TickerSymbol)
.IsRequired()
.HasMaxLength(10);

Property(s => s.OpenPrice)
.IsOptional()
.HasPrecision(8, 2);

HasMany(s => s.StockTrackerStocks)
.WithRequired(sts => sts.Stock)
.HasForeignKey(fk => fk.StockId);

ToTable("Stock");
}
}

With this in place EF has what it needs to translate a configuration class into DDL statements used to create or update a database schema.  Plus, EF tracks schema changes for you, and will automatically drop or add columns as the configuration class evolves.

Migration

For migrating data, EF provides an extensible base class DbMigrationsConfiguration, which requires a generic class of type DbContext.  This is where you would pass your custom implementation of DbContext, for this example StocksTrackerContext.

DbMigrationsConfiguration provides a single overridable method called Seed, passing the concrete implementation of DbContext.  From there any statements needed to “seed” the database with reference data or default users can be executed via the context.


/// <summary>
/// Class for migrating data to the Stocks Tracker domain.
/// </summary>
public class StocksTrackerMigrationsConfiguration: DbMigrationsConfiguration<StocksTrackerContext>

protected override void Seed(StocksTrackerContext context)
{
   // query, add, edit or delete data as needed
}

Initialization

With the context, configuration and migration classes in place, all that’s missing is a process that ties them all together.  Luckily, EF provides a number of options for initializing the database.  For Stocks Tracker I used the MigrateDatabaseToLatestVersion initializer, which is able to detect configuration changes and update the database schema accordingly (the Seed method will also be called during this process).


Database.SetInitializer(
 new MigrateDatabaseToLatestVersion<StocksTrackerContext, StocksTrackerMigrationsConfiguration>());

What’s nice about this is that the initialization can be called from within some bootstrapping code, or compiled into an executable called during an installation or some other process. I made a simple console project that initializes the database, then writes data to the console, so I know when there’s an error with the configuration or migration. The options EF provide make it easy to configure the database and context to work in development, testing or production environments.

Summing Up

As I mentioned, EF provides many options for creating a context that allows your application to connect to a data store, including the exact opposite of what the Stocks Tracker does (starting with a created and populated database, then generating configuration and context classes).

One cool option is Simon Hughes’ Reverse POCO Generator, which given an existing database will reverse engineer and generate POCO, configuration and context classes from tables and views, and will even generate classes for using stored procedures.  A familiarity with T4 templates is a big help, as most generators leverage T4s to some extent.

That was a lot to cover, but it’s enough for what I want Stocks Tracker to accomplish.  Next, we’ll look at the business layer.

Leave a comment