Change MigrationsHistoryTable column names in EF Core - entity-framework-core

I have a standardized all table and column names in my EF Core database to use snake_case. I was able to change the migrations history table name and schema to match the rest of the database, but I am not able to find a way to change the columns from MigrationId to migration_id and ProductVersion to product_version.
Any ideas on how this could be done?

Here is an example of how to do it on SQL Server.
First, create a custom implementation of SqlServerHistoryRepository overriding ConfigureTable.
class MyHistoryRepository : SqlServerHistoryRepository
{
public MyHistoryRepository(
IDatabaseCreator databaseCreator, IRawSqlCommandBuilder rawSqlCommandBuilder,
ISqlServerConnection connection, IDbContextOptions options,
IMigrationsModelDiffer modelDiffer,
IMigrationsSqlGenerator migrationsSqlGenerator,
IRelationalAnnotationProvider annotations,
ISqlGenerationHelper sqlGenerationHelper)
: base(databaseCreator, rawSqlCommandBuilder, connection, options,
modelDiffer, migrationsSqlGenerator, annotations, sqlGenerationHelper)
{
}
protected override void ConfigureTable(EntityTypeBuilder<HistoryRow> history)
{
base.ConfigureTable(history);
history.Property(h => h.MigrationId).HasColumnName("migration_id");
history.Property(h => h.ProductVersion).HasColumnName("product_version");
}
}
Then replace the replace the service with your custom implementation.
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options
.UseSqlServer(connectionString)
.ReplaceService<SqlServerHistoryRepository, MyHistoryRepository>();

Related

Update to .NET 5 DataAnnotations.Schema don't work anymore. LINQ join multiple dbs / querys failing

In Core 3.0 i could join multiple dbs / schemas.
Here is the class Order in dbEarth :
namespace dgNet.Core.Models.Earth
{
[Table("tbl_Order", Schema ="Earth")]
public class Order : EntityBaseWithTypedId<int>
{
[Key]
[Column("BestID")]
public override int Id { get; set; }
Here is class SerialNumber in dbMars
namespace dgNet.Core.Models.Mars
{
[Table("tbl_serialnumber", Schema = "Mars")]
public class SerialNumber : EntityBaseWithTypedId<int>
{
[Column("serialnumber")]
public int Serialnumber { get; set; }
[Column("jobId")]
public int JobId { get; set; }
[ForeignKey("JobId")]
public Order Order { get; set; }
Data Annotations is equivalent to the code here :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().ToTable("tbl_Order", "Earth");
}
So if builded a LINQ Query and included Order(dbEarth) in SerialNumber(dbMars) it worked well.
query => query.Include(serialNumber => serialNumber.Order).FirstOrDefault();
After the update to Core 5.0 SQL- Querys are created incorrectly.
SQL joins Orders on the same db / schema like SerialNumbers.
Using newest NuGet packages .AspCore (5.0.10)
DB = MySQL
Using Pomelo.EntityFrameworkCore.MySql (5.0.2)
Someone has that problem?
MySQL does not support the EF Core concept of schemas.
The EF Core concept of schemas is the same one that SQL Server uses, in which schemas are basically just categories (organization units) that you can use to group multiple tables logically together within the same database.
What MySQL calls schemas are actually databases, and a single DbContext does not support multiple databases in EF Core.
Therefore, we officially removed the very brittle multi-database support in Pomelo 3.2.0.
The official way to deal with this is shown in Implement alternatives to the current behavior to always throw, if a schema has been set for an object #982:
There are currently 3 options to choose from:
// Throw an exception, if a schema is being used. This is the default.
options.UseMySql(myConnectionString, b => b.SchemaBehavior(MySqlSchemaBehavior.Throw))
// Silently ignore any schema definitions.
options.UseMySql(myConnectionString, b => b.SchemaBehavior(MySqlSchemaBehavior.Ignore))
// Use the specified translator delegate to translate from an input schema and object name to
// an output object name whenever a schema is being used.
options.UseMySql(myConnectionString, b => b.SchemaBehavior(MySqlSchemaBehavior.Translate,
(schema, entity) => $"{schema ?? "dbo"}_{entity}"))
There is also a way to explicitly enable the old behavior, as illustrated in
method ModelBuilder.HasDefaultSchema is not working (No database selected)
#22971 (comment) for Pomelo 3.2.x:
[...]
In essence, there are two steps:
You need to derive from MySqlSqlGenerationHelper and override GetSchemaName:
public class CustomMySqlSqlGenerationHelper : MySqlSqlGenerationHelper
{
public CustomMySqlSqlGenerationHelper(
RelationalSqlGenerationHelperDependencies dependencies,
IMySqlOptions options)
: base(dependencies, options)
{
}
protected override string GetSchemaName(string name, string schema)
=> schema; // <-- this is the first part that is needed to map schemas to databases
}
You need to provide a schema name translator:
optionsBuilder
.UseInternalServiceProvider(serviceProvider) // use our ServiceProvider
.UseMySql(
"server=127.0.0.1;port=3308;user=root;password=;database=EFCoreIssue22971_01_IceCreamParlor",
b => b.ServerVersion("8.0.21-mysql")
.SchemaBehavior(
MySqlSchemaBehavior.Translate,
(schemaName, objectName) => objectName) // <-- this is the second part that is needed to map
// schemas to databases
.CharSetBehavior(CharSetBehavior.NeverAppend))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();

How to Customize Migration History Table with Entity Framework Core

I'm setting up Entity Framework Core in a new API to deploy to an existing SQL Server database that is used by Entity Framework 4.6 applications. There is one Migration History table that is shared by other applications, and has 2 fields in it that need to be populated for each entry: ContextKey, and Model. Entity Framework Core does not have a Context Key, and does not save the Model to the Migration History table.
I've already created a HistoryRepository : SqlServerHistoryRepository and configured Entity Framework Core to use it, but the ConfigureTable method only allows you to create additional columns, but not actually populate each record as it gets inserted with custom data. Providing a default value to the column is not a solution.
public class HistoryRepository : SqlServerHistoryRepository
{
public HistoryRepository(HistoryRepositoryDependencies dependencies)
: base(dependencies)
{
}
protected override void ConfigureTable(EntityTypeBuilder<HistoryRow> history)
{
base.ConfigureTable(history);
history.Property<string>("ContextKey")
.HasMaxLength(300);
history.Property<byte[]>("Model");
}
}
services.AddDbContext<MDSContext>(options =>
options.UseLazyLoadingProxies()
.UseSqlServer(
connectionString,
x => x.MigrationsHistoryTable("__MigrationHistory")).ReplaceService<Microsoft.EntityFrameworkCore.Migrations.IHistoryRepository, Burkhart.CoreServices.IncomingOrders.Core.Models.Base.HistoryRepository>()
);
I should be able to provide a custom value for ContextKey and Model dynamically
I looked all over for solutions, but they all show you how to add a column and set a default value, but not how to set a value dynamically. I ended up digging into the ASP.NET Entity Framework Core source code at GitHub for the solution, so that I would share it with everyone else, as I know there are others that are looking for this information:
Just override the GetInsertScript method on the HistoryRepository and insert your custom values. Here is the full solution:
public class HistoryRepository : SqlServerHistoryRepository
{
public HistoryRepository(HistoryRepositoryDependencies dependencies)
: base(dependencies)
{
}
protected override void ConfigureTable(EntityTypeBuilder<HistoryRow> history)
{
base.ConfigureTable(history);
history.Property<string>("ContextKey")
.HasMaxLength(300);
history.Property<byte[]>("Model");
}
public override string GetInsertScript(HistoryRow row)
{
var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string));
return new StringBuilder().Append("INSERT INTO ")
.Append(SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema))
.Append(" (")
.Append(SqlGenerationHelper.DelimitIdentifier(MigrationIdColumnName))
.Append(", ")
.Append(SqlGenerationHelper.DelimitIdentifier(ProductVersionColumnName))
.Append(", [ContextKey], [Model])")
.Append("VALUES (")
.Append(stringTypeMapping.GenerateSqlLiteral(row.MigrationId))
.Append(", ")
.Append(stringTypeMapping.GenerateSqlLiteral(row.ProductVersion))
.Append($", '{ContextConstants.ContextName}.{ContextConstants.ContextSchemaName}', 0x)")
.AppendLine(SqlGenerationHelper.StatementTerminator)
.ToString();
}
}
Here is a link to the source code on github:
https://github.com/aspnet/EntityFrameworkCore/blob/master/src/EFCore.Relational/Migrations/HistoryRepository.cs

Working with Schemas in EF Code First Migrations

I suppose this question is a cosmetic one; when you initially create an EF migration, it puts the schema in by default; for example:
public override void Up()
{
DropPrimaryKey("dbo.MyTable");
AddPrimaryKey("dbo.MyTable", "NewField");
This seems fine, unit you see the key name that it generates as a result (it has dbo in the key name).
I realise that one way around this is to specify the key name directly. Are there any other options, for example, can the schema be specified for a block, but not included in the specific modifications? For example:
public override void Up()
{
UseSchema("dbo");
DropPrimaryKey("MyTable");
AddPrimaryKey("MyTable", "NewField");
I realise that you can simply omit the schema name; i.e., this will work:
public override void Up()
{
DropPrimaryKey("MyTable");
AddPrimaryKey("MyTable", "NewField");
But how would I then deal with a situation where there were more than a single schema?
You can specify default schema using HasDefaultSchema method on DbModelBuilder class instance.
modelBuilder.HasDefaultSchema("schemaName");
You can also set schema for each entity using ToTable method on EntityTypeConfiguration<TEntityType> class instance. Which will generate migration scripts with provided schema for desired entity/ies.
modelBuilder.Entity<TEntity>().ToTable("tableName", "schemaName")
You can also use Table attribute to set schema for entity.
[Table("tableName","schemaName")]
Or you can write your own custom convention
public class DynamicSchemaConvention : Convention
{
public CustomSchemaConvention()
{
Types().Configure(c => c.ToTable(c.ClrType.Name, c.ClrType.Namespace.Substring(c.ClrType.Namespace.LastIndexOf('.') + 1)));
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(new CustomSchemaConvention());
}
Related links:
DbModelBuilder.HasDefaultSchema Method
EntityTypeConfiguration.ToTable Method
TableAttribute Class
Entity Framework 6 - Code First: table schema from classes' namespace
Entity Framework Custom Code First Conventions (EF6 onwards)

How change tables's schema of Identity Server 4?

By default the tables's schema of Identity Server 4 is dbo, i want change it to security, so i create ConfigurationContext which inherit from ConfigurationDbContext:
public class ConfigurationContext : ConfigurationDbContext
{
public ConfigurationContext(DbContextOptions<ConfigurationDbContext> options, ConfigurationStoreOptions storeOptions) : base(options, storeOptions)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("Security");
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var relationalOptions = RelationalOptionsExtension.Extract(optionsBuilder.Options);
relationalOptions.MigrationsHistoryTableSchema = "Security";
}
}
and in add-migration i use ConfigurationContext :
Add-Migration -c ConfigurationContext
but i got this error:
No parameterless constructor was found on 'ConfigurationContext'. Either add a parameterless constructor to 'ConfigurationContext' or add an implementation of 'IDbContextFactory' in the same assembly as 'ConfigurationContext'.
what is the problem?
IdentityServer4 provides this option. In ConfigureServices,
services.AddIdentityServer()
.AddOperationalStore(builder => builder.UseSqlServer(cnStr, options =>
options.MigrationsAssembly(migAssembly)),
storeOption => storeOption.DefaultSchema = "security")
This way, you can continue to use the IDbContextFactory as suggested in the quickstarts.
I know this is quite an old question, but I recently had a similar issue; June Lau's answer does provide some of the info you need to resolve this, but the important part is that migrations don't inspect the database context at runtime, so you need to define the schema before you create your database migration.
Don't worry about extending ConfigurationDbContext either, as that's not needed, just add something like this to your ConfigureServices method in Startup.cs:
var identityServerBuilder = services.AddIdentityServer(options =>
{
// ...
});
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
identityServerBuilder.AddConfigurationStore(options =>
{
options.DefaultSchema = "config";
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
});
Once you've added that code, create a migration for the relevant database context:
Add-Migration CreateInitialSchema -Context ConfigurationDbContext
You should see that the created migration starts like this:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "config");
migrationBuilder.CreateTable(
name: "ApiResources",
schema: "config",
columns: table => new ...
The problem is that Add-Migration -c ConfigurationContext command does not startup your application and thus does not know how to resolve the classes in your constructor:
public ConfigurationContext( //How do i resolve this, i dont know?
DbContextOptions<ConfigurationDbContext> options,
ConfigurationStoreOptions storeOptions)
: base(options, storeOptions)
{ }
You need to add a parameterless constructor, as the error suggests:
public ConfigurationContext()
: base(/* todo default static logic here */)
{ /* and here */ }
Why
The database migration tries to create an instance of the ConfigurationContext to determine the 'desired' state (the state you want your database to be after the database migration has been executed).
This migration is a static file inside your project saying which Columns and which indexes etc need to be added or removed to the database to create the 'desired' state.
This Add-Migration command simply reflects your code to find the right context, it does not go through your startup class to see which dependencies you have the find (this would become way to complex since there could also be runtime dependencies or dependencies based on App-settings, etc)

Can I add a discriminator via a custom code first convention?

I add a discriminator column to all my entities in order to facilitate soft delete.
Currently I do it by specifying it on each entity one by one:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Foo>().Map(m => m.Requires("IsDeleted").HasValue(false));
modelBuilder.Entity<Bar>().Map(m => m.Requires("IsDeleted").HasValue(false));
//etc etc
}
What I'd like to be able to do is specify it as a Custom Code First Convention. My entities all inherit from a ModelBase class. So I can create a custom convention to map to stored procedures like this:
modelBuilder.Types<ModelBase>().Configure(m => m.MapToStoredProcedures());
but this is not available:
modelBuilder.Types<ModelBase>().Configure(m => m.Requires("IsDeleted").HasValue(false));
So, is there any way to add a discriminator to all entities that inherit from ModelBase other than doing it one by one?