DemoDbContext
implementing our interface IDbContextSchema
from the first part of this series.
public interface IDbContextSchema
{
string Schema { get; }
}
public class DemoDbContext : DbContext, IDbContextSchema
{
public string Schema { get; }
public DbSet Products { get; set; }
...
}
At first we create a migration the usual way: dotnet ef migrations add Initial_Migration
And we get the following:
public partial class Initial_Migration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable("Products",
table => new { Id = table.Column() },
constraints: table => table.PrimaryKey("PK_Products", x => x.Id));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable("Products");
}
}
Next, we add a constructor to provide the migration with IDbContextSchema
and pass the schema to CreateTable
and DropTable
.
public partial class Initial_Migration : Migration
{
private readonly IDbContextSchema _schema;
public Initial_Migration(IDbContextSchema schema)
{
_schema = schema ?? throw new ArgumentNullException(nameof(schema));
}
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable("Products",
table => new { Id = table.Column() },
constraints: table => table.PrimaryKey("PK_Products", x => x.Id),
schema: _schema.Schema);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable("Products", _schema.Schema);
}
}
If we try to run the migration then we get a MissingMethodException: No parameterless constructor defined for this object.
because EF Core needs a parameterless constructor to be able to create an instance of the migration. Luckily, we can adjust the part that is responsible for the creation of new instances. For that we derive from MigrationsAssembly
and override the method CreateMigration
. In CreateMigration
we check if the migration requires an instance of IDbContextSchema
and whether the current DbContext
is implementing this interface. If so, then we create new instance of the migration by ourselves and return this instance to the caller, otherwise we pass the call to the default implementation.
public class DbSchemaAwareMigrationAssembly : MigrationsAssembly
{
private readonly DbContext _context;
public DbSchemaAwareMigrationAssembly(ICurrentDbContext currentContext,
IDbContextOptions options, IMigrationsIdGenerator idGenerator,
IDiagnosticsLogger logger)
: base(currentContext, options, idGenerator, logger)
{
_context = currentContext.Context;
}
public override Migration CreateMigration(TypeInfo migrationClass,
string activeProvider)
{
if (activeProvider == null)
throw new ArgumentNullException(nameof(activeProvider));
var hasCtorWithSchema = migrationClass
.GetConstructor(new[] { typeof(IDbContextSchema) }) != null;
if (hasCtorWithSchema && _context is IDbContextSchema schema)
{
var instance = (Migration)Activator.CreateInstance(migrationClass.AsType(), schema);
instance.ActiveProvider = activeProvider;
return instance;
}
return base.CreateMigration(migrationClass, activeProvider);
}
}
The last step is to register the DbSchemaAwareMigrationAssembly
with the dependency injection of EF Core.
Remarks: to change the schema (or the table name) of the migration history table you have to use the method MigrationsHistoryTable
var optionsBuilder = new DbContextOptionsBuilder()
.UseSqlServer("..."
// optional
//, b => b.MigrationsHistoryTable("__EFMigrationsHistory", schema)
)
.ReplaceService()
.ReplaceService();
That’s all!