At the moment there is no built-in support for changing the database schema at runtime. Luckily, Entity Framework Core (EF) provides us with the right tools to implement it by ourselves.

The demos are on GitHub: github:PawelGerr/EntityFrameworkCore-Demos

Given are a database context DemoDbContext and an entity Product.

public class DemoDbContext : DbContext
{
  public DbSet<Product> Products { get; set; }

  public DemoDbContext (DbContextOptions<DemoDbContext> options)
    : base(options)
  {
  }
}

public class Product
{
  public Guid Id { get; set; }
}

There are 2 ways to change the schema, either by applying the TableAttribute or by implementing the interface IEntityTypeConfiguration<TEntity>.

The first option won't help us because the schema is hard-coded.

[Table("Products", Schema = "demo")]
public class Product
{
  public Guid Id { get; set; }
}

The second option gives us the ability to provide the schema from DbContext to the EF model configuration. At first we implement the entity configuration for Product.

public class ProductEntityConfiguration : IEntityTypeConfiguration<Product>
{
  private readonly string _schema;

  public ProductEntityConfiguration(string schema)
  {
    _schema = schema;
  }

  public void Configure(EntityTypeBuilder<Product> builder)
  {
    if (!String.IsNullOrWhiteSpace(_schema))
      builder.ToTable(nameof(DemoDbContext.Products), _schema);

    builder.HasKey(product => product.Id);
  }
}

Now we use the entity configuration in OnModelCreating and pass the schema to it via constructor. Additionally, we create the interface IDbContextSchema containing just the schema (i.e. a string) to be able to inject it into DemoDbContext.

public interface IDbContextSchema
{
  string Schema { get; }
}

// DbContext implements IDbContextSchema as well
// so we know it is "schema-aware"
public class DemoDbContext : DbContext, IDbContextSchema
{
 public string Schema { get; }

 public DbSet<Product> Products { get; set; }

 public DemoDbContext(DbContextOptions<DemoDbContext> options,
                      IDbContextSchema schema = null)
 : base(options)
 {
   Schema = schema?.Schema;
 }

 protected override void OnModelCreating(ModelBuilder modelBuilder)
 {
   base.OnModelCreating(modelBuilder);

   modelBuilder.ApplyConfiguration(new ProductEntityConfiguration(Schema));
 }
}

We are almost done. The last task is to change how EF is caching database model definitions. By default just the type of the DbContext is used but we need to differentiate the models not just by type but by the schema as well. For that we implement the interface IModelCacheKeyFactory.

public class DbSchemaAwareModelCacheKeyFactory : IModelCacheKeyFactory
{
  public object Create(DbContext context)
  {
    return new {
        Type = context.GetType(),
        Schema = context is IDbContextSchema schema ? schema.Schema : null
    };
  }
}

No we have to replace the default implementation with ours and to register the IDbContextSchema. In current example the IDbContextSchema is just a singleton but it can be provided by anything we want like read from a database or derived from a JWT bearer token during an HTTP request, etc.

IServiceCollection services = ...;

services
  .AddDbContext<DemoDbContext>(
      builder => builder.UseSqlServer("...")
                  .ReplaceService<IModelCacheKeyFactory, DbSchemaAwareModelCacheKeyFactory>())
  .AddSingleton<IDbContextSchema>(new DbContextSchema("demo"));

--------------------------------------------

// just a helper class
public class DbContextSchema : IDbContextSchema
{
  public string Schema { get; }

  public DbContextSchema(string schema)
  {
    Schema = schema ?? throw new ArgumentNullException(nameof(schema));
  }
}

Voila! 

PS: There is one special use case for that feature - isolation of integration tests due to missing support of ambient transactions. For that we need schema-aware migrations we will look at in the next blog post.

Stay tuned!

Related Articles

entity framework core
Is "N+1 Queries" still a performance issue in Entity Framework Core 3?
In a previous post we saw that EF 2.1 is highly susceptible to the N+1 queries problem. After the release of a new version of Entity Framework Core (EF) the first question coming to mind is: "Is it still a big issue in EF 3.1?" And if the answer is no, is there anything else we…
Pawel Gerr
entity framework core
Entity Framework Core 3.0 - "Hidden" GROUP BY Capabilities (Part 2)
In the previous blog post we used a navigational property to work around the limitations of the extension method . The problem is, there is not always such a property we can use for grouping. Especially, when following domain driven design practices, a bidirectional navigation is…
Pawel Gerr
entity framework core
Entity Framework Core 3.0 - "Hidden" GROUP BY Capabilities (Part 1)
With Entity Framework Core 3.0 (EF) the internal implementation of the LINQ query translation has been changed a lot. So, some queries that didn't work previously are working now and some that worked are not working anymore. :) The LINQ extension method is a special one…
Pawel Gerr
entity framework core
Entity Framework Core - Getting more Deadlock Information with named Transactions
Whether in an application in production or in integration tests on CI, the deadlocks are virtually unavoidable. The behavior of a deadlock depends on the database (MS SQL Server, MySQL, etc)  and the isolation level (like Snapshot Isolation). Some of the databases are blocking…
Pawel Gerr