I spent my summer internship at Microsoft to building a piece of software that allows Entity Framework 7 to connect with Azure Table Storage accounts. In the process of creating this, I attempted to document the interface between the provider and the core APIs of EF. This post includes technical details that should enable third-parties to create their own provider for Entity Framework.
Note: this content is accurate as of the today (July 19), but these APIs are subject to change. This content is also available on the Entity Framework wiki.
EF provides a set of Core APIs and services that make it easier to manage the interaction of in-memory objects and persistent data storage. This core is agnostic of all data store specific details, and is used by all of EF supported providers (SQL Server, SQLite, and Azure Table Storage). Using these Core APIs, it is possible to create a provider for additional data store types.
An EF provider must implement a set of APIs to manage reading from and writing to a persistent data store. See diagrams and reference below for some detail on how the classes interact with the EF core.
EF providers must implement the following abstract classes.
(All reference names in this document are relative to Microsoft.Data.Entity)
To help end-users discover provider-specific features via IntelliSense, we recommend providing extension methods on some of the default EF classes.
Getting Started: Sample Project
See this repository to get a starter project for writing a new EF provider.
This class configures the dependency injection settings used in an instance of DbContext. It is used during DbContext initialization to configure the DbContextOptions.
This class is the hub of activity for a provider. A data store must implement these methods:
SaveChanges receives a list of entities which must be persisted to the data store. Upon successful completion, return the number of entities successfully saved. If an entity cannot be saved, this method should throw an exception containing the reason why the action failed.
Query receives a QueryModel containing a query to execute against the data store. This method should return an IEnumerable from the data store of all entites matching the query model. QueryModel is a simplified, but equivalent expression tree that represents the original LINQ query requested by user code on DbSet. (Thanks relinq!)
This class manages connection settings and sessions.
Although an EF provider requires an implementation, the details of how the class operates is entirely up to the provider i.e. time to freestyle.
For example, SQL Server uses this class to open TCP/IP connections, but SQLite uses this class to create connections to the local filesystem.
An implementation of requires implementation of the following methods.
EnsureCreated ensures that the database/tables/containers needed to store the model on the server exists and are write-accessible. EnsureDeleted ensures the database/tables/containers are deleted.
Both methods return true when the method call changed something on the server — e.g. created a new database, deleted a table — and return false when the model has already been created/deleted.
This class configures Entity Framework to use provider-specific implementations of theses classes:
These classes are not abstract. A default implementation has been provided.
Because this is fairly straightforward class, we have provided an abstract implementation that makes it easier to use dependency injection.
DatastoreSource<TDataStore, TDbContextOptionsExtension, TCreator, TConnection, TValueGeneratorCache, TDatabase>
This generic class requests an implementation of the types classes from ServiceCollection.
These classes do not need to be implemented, but they are a way to extend provider features.
This class provides access to APIs custom to the database of the provider. This class is best used as a proxy to DatastoreCreator which should contain the logic for manipulating the structure of the data store.
Example: SQLite uses this to provide APIs for creating/deleting a database file. In Azure Table Storage, it provides APIs for creating/deleting tables on the server.
This class is used to control value generators. Value generators are used by EF7 to populate fields, such as auto-incremented IDs, that are generated on the server or client.
These classes exist in EF’s Core, but users may not know how to directly interact with them to configure their application. By creating custom extension methods, IntelliSense in Visual Studio will show users custom methods to configure the provider. IntelliSense works best the extension methods are defined in the namespace of the class they extend.
Expose access to your provider within the OnConfiguring method of DbContext. Your extension method should add your provider-specific implementation of DbContextOptionsExtension to an instance of DbContextOptions.
Power user note: To add a DbContextOptionsExtension to DbContextOptions,
you must first cast DbContextOptions to IDbContextOptionsExtensions, and then use
Example: This will help users configure a context that connects to Azure Table Storage.
Expose access to dependency injection configuration. This extension should configure DI to use provider-specific implementations.
Example: Configure DI to use Azure Table Storage’s implementations
Expose the provider-specific database calls with an extension method that safely casts Storage.Database to the provider implementation.
Example: This will light-up the APIs unique to SQL. A user will see this extension method listed in IntelliSense when accessing DbContext.Database
relinq is a third party library used by Entity Framework to simplify interaction with LINQ queries. All LINQ queries against DbSet run through a set of expression tree visitors that have been simplified by relinq. A provider may read and/or change the expression tree it receives in Storage.Datastore.Query()