EF Core 1.1

So you want to write an EF Core provider...

Writing a database provider for EF Core can be daunting. I have written several posts over the last month containing information you need to know when writing a provider. This post pulls all that together to give an overview of the different building blocks with links to previous posts containing the details.

Step 1: Implement services

Implement IDatabaseProviderServices

For a non-relational provider, create a class MyProviderDatabaseProviderServices that inherits from DatabaseProviderServices and override its properties. The abstract properties define services for which a provider must supply an implementation. You may also need to override some of the virtual properties to return provider-specific implementations.

Do the same for a relational provider, except your class should inherit from RelationalDatabaseProviderServices.

The properties should be implemented as GetService calls for your provider's implementation of the service. For example:

public class SqlServerDatabaseProviderServices : RelationalDatabaseProviderServices
{
    public SqlServerDatabaseProviderServices(IServiceProvider services)
        : base(services)
    {
    }

    public override IValueGeneratorSelector ValueGeneratorSelector
        => GetService<SqlServerValueGeneratorSelector>();

    public override IRelationalDatabaseCreator RelationalDatabaseCreator
        => GetService<SqlServerDatabaseCreator>();

    // More...
}

Create an "AddEntityFramework..." extension method

Create an extension method on IServiceCollection called "AddEntityFrameworkMyProvider". For example, AddEntityFrameworkSqlServer. This method should:

For example:

public static IServiceCollection AddEntityFrameworkSqlServer(this IServiceCollection services)
{
    services.AddRelational();

    services.TryAddEnumerable(ServiceDescriptor
        .Singleton<IDatabaseProvider, DatabaseProvider<SqlServerDatabaseProviderServices, SqlServerOptionsExtension>>());

    services.TryAdd(new ServiceCollection()
        .AddSingleton<SqlServerTypeMapper>()
        .AddScoped<SqlServerValueGeneratorSelector>()
        .AddScoped<SqlServerDatabaseCreator>()
        // More...
        );

    return services;
}

See this post for more information on provider services.

Step 2: Implement a 'Use...' method

Create an options extension

Create a class MyProviderOptionsExtension that implements IDbContextOptionsExtension. This class should:

When implementing a relational provider your class should inherit from RelationalOptionsExtension. For example:

public class SqlServerOptionsExtension : RelationalOptionsExtension
{
    private bool? _rowNumberPaging;

    public SqlServerOptionsExtension()
    {
    }

    public SqlServerOptionsExtension([NotNull] SqlServerOptionsExtension copyFrom)
        : base(copyFrom)
    {
        _rowNumberPaging = copyFrom._rowNumberPaging;
    }

    public virtual bool? RowNumberPaging
    {
        get { return _rowNumberPaging; }
        set { _rowNumberPaging = value; }
    }

    public override void ApplyServices(IServiceCollection services)
        => services.AddEntityFrameworkSqlServer();
}

Create a 'Use...' method

Create an extension method on DbContextOptionsBuilder called UseMyProvider. This method should:

For example:

public static DbContextOptionsBuilder UseSqlServer(
    this DbContextOptionsBuilder optionsBuilder,
    string connectionString,
    Action<SqlServerDbContextOptionsBuilder> sqlServerOptionsAction = null)
{
    var extension = GetOrCreateExtension(optionsBuilder);
    extension.ConnectionString = connectionString;
    ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);

    sqlServerOptionsAction?.Invoke(new SqlServerDbContextOptionsBuilder(optionsBuilder));

    return optionsBuilder;
}

public static DbContextOptionsBuilder<TContext> UseSqlServer<TContext>(
    this DbContextOptionsBuilder<TContext> optionsBuilder,
    string connectionString,
    Action<SqlServerDbContextOptionsBuilder> sqlServerOptionsAction = null)
    where TContext : DbContext
    => (DbContextOptionsBuilder<TContext>)UseSqlServer(
        (DbContextOptionsBuilder)optionsBuilder, connectionString, sqlServerOptionsAction);

See this post for more details on options extensions and creating a 'Use...' method.

Step 3: Create metadata extension methods

Define annotations

Create an annotation prefix name for your provider and names for any provider-specific metadata. Use these names to create a MyProviderFullAnnotationNames class, inheriting from RelationalFullAnnotationNames if your provider is relational. For example:

public class SqlServerFullAnnotationNames : RelationalFullAnnotationNames
{
    protected SqlServerFullAnnotationNames(string prefix)
        : base(prefix)
    {
        Clustered = prefix + SqlServerAnnotationNames.Clustered;
        MemoryOptimized = prefix + SqlServerAnnotationNames.MemoryOptimized;
    }

    public new static SqlServerFullAnnotationNames Instance { get; } 
        = new SqlServerFullAnnotationNames(SqlServerAnnotationNames.Prefix);

    public readonly string Clustered;
    public readonly string MemoryOptimized;
}

Create MyProvider() extension methods

Create extension methods named after your provider for each mutable and immutable metadata type. For example:

public static SqlServerEntityTypeAnnotations SqlServer(this IMutableEntityType entityType)
    => (SqlServerEntityTypeAnnotations)SqlServer((IEntityType)entityType);

public static ISqlServerEntityTypeAnnotations SqlServer(this IEntityType entityType)
    => new SqlServerEntityTypeAnnotations(entityType);

public static SqlServerPropertyAnnotations SqlServer(this IMutableProperty property)
    => (SqlServerPropertyAnnotations)SqlServer((IProperty)property);

public static ISqlServerPropertyAnnotations SqlServer(this IProperty property)
    => new SqlServerPropertyAnnotations(property);

Create the IMyProviderXxxAnnotations interfaces returned from these methods, inheriting from IRelationalXxxAnnotations for relational providers. Create classes that implement these interfaces, inheriting from RelationalXxxAnnotations for relational providers. For example:

public interface ISqlServerEntityTypeAnnotations : IRelationalEntityTypeAnnotations
{
    bool IsMemoryOptimized { get; }
}

public class SqlServerEntityTypeAnnotations
    : RelationalEntityTypeAnnotations, ISqlServerEntityTypeAnnotations
{
    public SqlServerEntityTypeAnnotations(IEntityType entityType)
        : base(entityType, SqlServerFullAnnotationNames.Instance)
    {
    }

    public virtual bool IsMemoryOptimized
    {
        get { return EntityType[SqlServerFullAnnotationNames.Instance.MemoryOptimized] as bool? ?? false; }
        set { ((IMutableAnnotatable)EntityType)[SqlServerFullAnnotationNames.Instance.MemoryOptimized] = value; }
    }
}

Make sure that the interface defines read-only properties, while the class adds in setters. The interface is used for immutable metadata, while the class is used with mutable metadata.

Create fluent API extensions

Create fluent API extension methods that make use of the core metadata extensions described above. These methods should be named MyProviderDoSomething. Make sure to include generic and non-generic overloads of these methods. For example:

public static class SqlServerEntityTypeBuilderExtensions
{
    public static EntityTypeBuilder ForSqlServerIsMemoryOptimized(
        this EntityTypeBuilder entityTypeBuilder, bool memoryOptimized = true)
    {
        entityTypeBuilder.Metadata.SqlServer().IsMemoryOptimized = memoryOptimized;

        return entityTypeBuilder;
    }

    public static EntityTypeBuilder<TEntity> ForSqlServerIsMemoryOptimized<TEntity>(
        this EntityTypeBuilder<TEntity> entityTypeBuilder, bool memoryOptimized = true)
        where TEntity : class
        => (EntityTypeBuilder<TEntity>)ForSqlServerIsMemoryOptimized((EntityTypeBuilder)entityTypeBuilder, memoryOptimized);
}

Note that these methods can contain logic to obtain their values from the model hierarchy and to return reasonable defaults.

See this post for more information on metadata extensions.

Look at examples

The EF Core codebase on GitHub contains relational providers for SQL Server, SQLite, and a non-relational in-memory store. This code is the best place to look for guidance on how to implement a provider.

Summary

Creating an EF Core database provider requires implementation of many services together with extension methods to integrate with the API surface. Following the patterns outlined in this post will result in a provider that is consistent with the experience for other providers making it easy for developers to switch between different providers.


This page is up-to-date as of November 11th, 2016. Some things change. Some things stay the same. Use your noggin.