EF7 Provider Building Blocks

In this post I’ll outline the basic building blocks needed for an EF7 provider. The idea is not to show how everything should be implemented, but rather to show what pieces are needed and how they fit together. The best examples of EF7 providers are the SQL Server and SQLite providers, which ca both be found in the EF repro on GitHub.

EF7 providers should be shipped as NuGet packages. This post does not cover NuGet packaging, but you can look at the GitHib repro for some ideas on how to do this.

Note that EF7 is still very much pre-release software and the types and APIs described here are likely to change as we evolve towards the RTM release.

Dependency Injection

EF7 makes extensive use of dependency injection (DI). That is, EF is a collection of services that work together and depend on each other largely through constructor injection. Implementing an EF provider is really a matter of creating provider-specific implementations of these services as needed.

Context scopes

A single EF7 application can make use of multiple providers. For example, the same application could access a SQL Server database and a SQLite database. However, the session defined by each context instance is restricted to using a single provider. So you can create one context instance to access SQL Server and another context instance to access SQLite, but you cannot create one context instance and access both SQLite and SQL Server with that single instance.

This is achieved through each context instance creating a new DI scope. Services for the provider in use are then resolved through that scope using the mechanisms outlined in this post.

Types of services

EF7 has three broad categories of services:

  • Core services, which are not intended to be implemented differently for different providers.
  • Provider-specific services for which there are no common concrete implementations. All providers must provide implementations of these services. However, there are often abstract base classes that can be used as the basis for provider-specific implementations.
  • Provider-specific services for which there are common concrete implementations. For these services, a provider only needs to provide an implementation if the common implementation needs to be changed in some way.

IDataStoreServices

As stated above, the selection of which provider to use for a given context instance is done by creating a new DI scope. Provider-specific services are then resolved for the scope using delegates that map from the contract interface to the provider-specific implementation. This mapping is done using an implementation of the IDataStoreServices interface, and therefore each provider must create its own implementation of this interface and register it with DI.

For relational providers there is an IRelationalDataStoreServices interface which adds additional services used by the EF7 relational infrastructure. Providers that connection to a relational database should implement this interface.

There are base implementations of IRelationalDataStoreServices and IDataStoreServices (called RelationalDataStoreServices and DataStoreServices respectively) that contain predefined mappings for provider-specific services for which there are common concrete implementations. Providers should generally use one of these base classes and only override virtual methods as needed.

For example, here is the implementation of IRelationalDataStoreServices used by the SQLite provider, as of the time of writing:

public class SqliteDataStoreServices : RelationalDataStoreServices
{
    public SqliteDataStoreServices(IServiceProvider services)
        : base(services)
    {
    }

    public override IDataStoreConnection Connection 
        => GetService<SqliteDataStoreConnection>();

    public override IDataStoreCreator Creator 
        => GetService<SqliteDataStoreCreator>();

    public override IHistoryRepository HistoryRepository 
        => GetService<SqliteHistoryRepository>();

    public override IMigrationSqlGenerator MigrationSqlGenerator 
        => GetService<SqliteMigrationSqlGenerator>();

    public override IModelSource ModelSource 
        => GetService<SqliteModelSource>();

    public override IRelationalConnection RelationalConnection 
        => GetService<SqliteDataStoreConnection>();

    public override ISqlGenerator SqlGenerator 
        => GetService<SqliteSqlGenerator>();

    public override IDataStore Store 
        => GetService<SqliteDataStore>();

    public override IValueGeneratorCache ValueGeneratorCache 
        => GetService<SqliteValueGeneratorCache>();

    public override IRelationalTypeMapper TypeMapper 
        => GetService<SqliteTypeMapper>();

    public override IModificationCommandBatchFactory ModificationCommandBatchFactory 
        => GetService<SqliteModificationCommandBatchFactory>();

    public override ICommandBatchPreparer CommandBatchPreparer 
        => GetService<SqliteCommandBatchPreparer>();

    public override IRelationalDataStoreCreator RelationalDataStoreCreator 
        => GetService<SqliteDataStoreCreator>();
}

Notice that it derives from RelationalDataStoreServices to use common services where possible and overrides for all services where a custom implementation is required.

Also notice that each property is mapping a concrete type in the GetService call to the contract interface that other services will depend on. That is, services should take the contract interface, not the concrete implementation, in their constructors so that all services are programmed against the contracts of other services.

Registering services

Each of the concrete services must be registered in the DI container so that the GetService calls will have something to find. This is done in an AddXxx extension method, where Xxx is the name of the provider. For example, the extension method for SQLite looks like this:

public static EntityFrameworkServicesBuilder AddSqlite(
    this EntityFrameworkServicesBuilder services)
{
    ((IAccessor<IServiceCollection>)services.AddRelational()).Service
        .AddSingleton<IDataStoreSource, SqliteDataStoreSource>()
        .TryAdd(new ServiceCollection()
            .AddSingleton<SqliteValueGeneratorCache>()
            .AddSingleton<SqliteSqlGenerator>()
            .AddScoped<SqliteTypeMapper>()
            .AddScoped<SqliteModificationCommandBatchFactory>()
            .AddScoped<SqliteCommandBatchPreparer>()
            .AddSingleton<SqliteModelSource>()
            .AddScoped<SqliteDataStoreServices>()
            .AddScoped<SqliteDataStore>()
            .AddScoped<SqliteDataStoreConnection>()
            .AddScoped<SqliteMigrationSqlGenerator>()
            .AddScoped<SqliteDataStoreCreator>()
            .AddScoped<SqliteHistoryRepository>());

    return services;
}

The AddXxx method is an extension method on EntityFrameworkServicesBuilder and should return the EntityFrameworkServicesBuilder that is passed in. This allows it to be chained from an AddEntityFramework call when registering DI services and allows additional AddXxx methods to be chained from it.

For a relational provider, a call To AddRelational is required to add all common relational services.

The registration of an IDataStoreSource facilitates selection of a given provider as described below. The remaining calls register the concrete services that will be resolved by the provider’s IDataStoreServices implementation.

Registration scopes

In most cases provider-specific services should be registered in DI as “scoped” using AddScoped. This is because the context creates scopes in order to use different providers for different context instances. Also, any service that depends on a scoped service must itself be registered as scoped.

In a few cases services can be registered using AddSingleton where the service does not depend on any other scoped service and a single instance can be used concurrently by all context instances–for example, the SqliteSqlGenerator in the code above. Note that all singleton services must be thread-safe.

There are currently two services (IModelSource and IValueGeneratorCache) which act as provider-specific caches. These services must be registered as singletons so that the cache persists across context instances, which is the point of having the cache. They therefore cannot depend on any scoped services. Also, each provider must use its own concrete implementation for these services so that each provider gets a different instance and cache collisions are avoided.

Provider selection

EF7 applications choose which provider to use for a given context instance my making a call to a UseXxx method, typically in the OnConfiguring method of the context. For example, to use SQLite an application might do something like this:

protected override void OnConfiguring(
    DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlite("MyDb.db");
}

The UseSqlite method is an extension method on DbContextOptionsBuilder. Its job is to create (or update an existing) IDbContextOptionsExtension object and register it onto the options builder. For example:

public static SqliteDbContextOptionsBuilder UseSqlite(
    this DbContextOptionsBuilder options,
    string connectionString)
{
    var extension = GetOrCreateExtension(options);
    extension.ConnectionString = connectionString;
    ((IOptionsBuilderExtender)options).AddOrUpdateExtension(extension);

    return new SqliteDbContextOptionsBuilder(options);
}

Note that a new builder with the updated options is returned to allow chaining of further configuration.

Presence of IDbContextOptionsExtension in the options builder is used by the EF stack to determine that a provider has been selected. It is also used to store provider-specific configuration for the session. Some of this configuration, like the connection string, is handled by the relational base class.

An IDbContextOptionsExtension is also used to register DI services automatically for the case where EF is taking care of all DI registration internally. An example implementation for SQLite (trimmed) looks something like:

public class SqliteOptionsExtension : RelationalOptionsExtension
{
    public override void ApplyServices(
        EntityFrameworkServicesBuilder builder)
        => builder.AddSqlite();
}

When EF needs to register the current provider’s services it calls ApplyServices, which in turn calls the AddXxx extension method that was defined above.

IDataStoreSource

The final piece of the puzzle that connects selection of a provider with the services used for that provider is an implementation of the IDataStoreSource interface. A typical implementation uses the DataStoreSource generic base class, passing in the types for the provider’s IDataStoreServices and IDbContextOptionsExtension implementations. For example, SqliteDataStoreSource looks something like:

public class SqliteDataStoreSource 
    : DataStoreSource<SqliteDataStoreServices, SqliteOptionsExtension>
{
    public override void AutoConfigure(DbContextOptionsBuilder optionsBuilder)
    {
    }

    public override string Name => "SQLite Data Store";
}

SqliteDataStoreSource is then registered in the AddSqlite extension method as shown above.

All of this means that when EF finds that a SqliteOptionsExtension has been added to the options builder through a call to UseSqlite, then SqliteDataStoreServices will be used to resolve services for the scope of that context instance.

Summary

The steps for creating an EF7 provider are:

  • Implement provider-specific services, using IDataStoreServices and IRelationalDataStoreServices as a guide for the services needed
  • Create an implementation of IDataStoreServices or IRelationalDataStoreServices to map your implementations, using one of the base classes as a starting point
  • Create an AddXxx extension method to register your services in DI
  • Create an IDbContextOptionsExtension implementation to handle provider selection and configuration
  • Create a UseXxx extension method to allow applications to select your provider
  • Create an IDataStoreSource implementation to tie everything together

Of course, the hard part about creating any provider is actually implementing the services. Also, you will likely want to create some extension methods for provider-specific model building and/or runtime functionality. If there is enough interest I may blog about these things at a later date.

Advertisements

10 comments on “EF7 Provider Building Blocks

  1. Looking forward to the next installment in this series ;-)

  2. When you say “you will likely want to create some extension methods for provider-specific model building and/or runtime functionality” It includes LINQ extensions. I mean Graph dbs can provide extra methods more specific than the ones provided by LINQ to move between nodes of a db?

    Looking forward to hear more info related extension methods

  3. I would expect that EF7 document db or hibrid(graph+document db) providers would offer its own change tracker provider. Those ones will provide a lighter change tracking strategy aware of the nature of documents (on change to a property should mark all the object properties as modified, so, no extra checkings for the other properties). Does it have any sense for you?

  4. Clement says:

    Thanks for the past.
    Thread safe services? Will we be able to use the same db context from multiple threads, unlike ef6? I hope so. It will simplify parallel queries where i need local modifications that are not persisted yet to be visible to all parallel queries.

    Otherwise could you do a post on the ef7 story regarding batch saving? I think I’ve seen it mentioned somewhere… I think ef6 is an awesome orm but personally the lack of batch saving/updates is the biggest shortcoming..

    • @Clement I plan on doing some more EF Core posts soon. I’ll look into one on batching. The EF Core DbContext is still not thread safe. The reason being it would add significant performance overhead to make it thread safe which would be unacceptable to those who don’t need it to be, which is most scenarios. So if you do want to use it from multiple threads then its best to do your own locking.

  5. WarrenP says:

    I friggin’ love that there’s an ASCII art Unicorn on the EF commandline interface inside “dnx . ef ….” in Visual Studio 2015. That part is awesome.

    You know what’s not awesome? The Visual Studio 2015 UI experience, of trying to invoke that EF command-line inside the IDE.

    My StackOverflow question:
    http://stackoverflow.com/questions/30851344/how-can-i-do-an-entity-framework-migration-with-visual-studio-2015-nuget-prompt

    You know what though? EF7 is looking awesome, and the whole vNext is pretty darn great.

    Cheers.

    Warren

  6. grldsndrs says:

    Thanks for this post. I have been using it to try and develop a Provider for MySql. I have some interest in reading your thoughts about a MySql provider for EF7 beta5. Can you post some guidelines?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s