Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allow the end-user to switch the Entity Framework provider at runtime

Consider that I have configured EF with a .NET Core web app:

services.AddDbContext<ApplicationDbContext>(options => 
    options.UseSqlServer(...));

I can also download a package to support for example SQLite:

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(...));

How can we allow a user to "select" the provider on app install? I mean - for example, in WordPress you can choose from a dropdown.

Is this possible in .NET Core? The only way I see is to restart the app only...

like image 227
pwas Avatar asked Nov 18 '16 23:11

pwas


People also ask

What is DbSet in C#?

A DbSet represents the collection of all entities in the context, or that can be queried from the database, of a given type. DbSet objects are created from a DbContext using the DbContext.

How do I change the EDMX connection string?

Open the edmx (go to properties, the connection string should be blank), close the edmx file again. Open the app. config and uncomment the connection string (save file) Open the edmx, go to properties, you should see the connection string uptated!!

What is DbContext and DbSet in Entity Framework?

DbContext generally represents a database connection and a set of tables. DbSet is used to represent a table.

What is OnModelCreating in Entity Framework?

The DbContext class has a method called OnModelCreating that takes an instance of ModelBuilder as a parameter. This method is called by the framework when your context is first created to build the model and its mappings in memory.


1 Answers

Here is an example on how you can implement a DbContextFactory or a DbContextProxy<T> which will create the correct provider and return it.

public interface IDbContextFactory
{
    ApplicationContext Create();
}

public class DbContextFactory() : IDbContextFactory, IDisposable
{
    private ApplicationContext context;
    private bool disposing;

    public DbContextFactory()
    {
    }

    public ApplicationContext Create() 
    {
        if(this.context==null) 
        {
            // Get this value from some configuration
            string providerType = ...;
            // and the connection string for the database
            string connectionString = ...;

            var dbContextBuilder = new DbContextOptionsBuilder();
            if(providerType == "MSSQL") 
            {
                dbContextBuilder.UseSqlServer(connectionString);
            }
            else if(providerType == "Sqlite")
            {
                dbContextBuilder.UseSqlite(connectionString);
            }
            else 
            {
                throw new InvalidOperationException("Invalid providerType");
            }

            this.context = new ApplicationContext(dbContextBuilder);
        }

        return this.context;
    }

    public void Dispose(){
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing){
        if (disposing){
            disposing?.Dispose();
        }
    }
}

Also make sure you implement the disposable pattern as show above, so the context gets disposed as soon as the factory gets disposed, to prevent the DbContext remaining in memory longer than necessary and free unmanaged resources as soon as possible.

Finally register the factory as scoped, as you would the context itself:

services.AddScopedd<IDbContextFactory, DbContextFactory>();

A more advanced and generic/extendable approach is by creating a IDbContextProxy<T> class which uses a bit of reflection to get the correct constructor and the DbContextOptionsBuilder to it.

Also possible to create a IDbContextBuilder which abstracts the provider creation.

public class SqlServerDbContextBuilder IDbContextBuilder
{
    public bool CanHandle(string providerType) => providerType == "SqlServer";

    public T CreateDbContext<T>(connectionString)
    {
        T context = ... // Create the context here

        return context;
    }
}

Then you can pick the correct provider w/o a hard coded if/else or switch block just by doing

// Inject "IEnumerable<IDbContextBuilder> builders" via constructor
var providerType = "SqlServer";
var builder = builders.Where(builder => builder.CanHandle(providerType)).First();
var context = builder.CreateDbContext<ApplicationContext>(connectionString);

and adding new types of provider is as easy as adding the dependencies and an XxxDbContextBuilder class.

See here, here or here for more information about this and similar approaches.

like image 159
Tseng Avatar answered Nov 15 '22 00:11

Tseng