Ef Core花里胡哨系列(2) 移除外键、扩展操作

发布时间 2024-01-02 12:14:15作者: 胖纸不争

Ef Core花里胡哨系列(2) 移除外键、扩展操作

虽然数据库的外键有着举足轻重的作用,但是在通常的软件设计中,很多人嫌弃麻烦,从而放弃了Ef CoreCodo-First模式而转向Db-First模式。但是Db-First不是很严谨,所以我这里就是提供一种相对折中的方式:在Code-Frist的模式下忽略外键的生成。

总之,外键在数据库中起着重要的作用,可以确保数据的完整性和一致性,简化数据查询和操作,并帮助建立数据库的关系模型。其实还是很有必要的。

重写Ef Core的迁移应用程序

我们无法对Ef Core生成迁移的部分进行操作,或者说操作非常困难,但是我们可以通过重写Ef Core中的MigrationsSqlGenerator来实现迁移文件在向Sql转义时的操作。

public class CustomMigrationsSqlGenerator : MigrationsSqlGenerator
{
    public static ISqlManager SqlManager =>
        SqlManagerHelper.GetSqlManager(AppSettings.Get<DbOptions>()?.GetUseableWriteHost()?.DbType);

    public CustomMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IMigrationsAnnotationProvider migrationsAnnotations) : base(dependencies)
    {
    }

    protected override void Generate(Microsoft.EntityFrameworkCore.Migrations.Operations.CreateTableOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true)
    {
        operation.ForeignKeys.Clear();
        base.Generate(operation, model, builder, terminate);
    }

    protected override void Generate(AddForeignKeyOperation operation, IModel? model, MigrationCommandListBuilder builder,
        bool terminate = true)
    {
        return;
    }
}

替换Ef Core中的默认实现

services.AddDbContext<SampleDbContext>(opts =>
{
    opts.ReplaceService<IMigrationsSqlGenerator, CustomMigrationsSqlGenerator>();
});

扩展操作

SqlManager是我自己封装的一个Sql生成相关的类,与逻辑无关,可以自己根据需要自行封装。

1 修改表

Ef Core中微软的官方实现为在修改列时,操作为先删除列再添加,我们可以通过重写对应的方法来实现直接修改的操作。

具体就是可以自己根据operation生成Sql使用builder.Append(sql).AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator).EndCommand();来执行。

一些社区的provider是实现了对应的修改方法的,但是微软官方库没有,可以使用这种方法补充。

protected override void Generate(AlterColumnOperation operation, IModel? model, MigrationCommandListBuilder builder)
{
    var sql = SqlManager.Block(SqlManager.GetAlterColumnSql(control, operation.Table,
                oldControl, true)).TrimEnd(';', '\n', '\r');

    builder.Append(sql).AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator).EndCommand();
}

2 备份表

在进行比较具有风险的迁移操作时,可以采用上面类似的方式,将表迁移前的数据和结构导入到一个历史表中。

GetBackupTableSql即根据当前表名将数据导入一个按操作时间命名的表名,例如User20240102

protected override void Generate(DropTableOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true)
{
    // 删除表和列时会对整个表进行备份,防止使用Migrations的时候误删除
    var sql = SqlManager.Block(SqlManager.GetBackupTableSql(operation.Name)).TrimEnd(';', '\r', '\n');

    builder.Append(sql).AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator).EndCommand();

    base.Generate(operation, model, builder, terminate);
}

protected override void Generate(DropColumnOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true)
{
    // 删除表和列时会对整个表进行备份,防止使用Migrations的时候误删除
    var sql = SqlManager.Block(SqlManager.GetBackupTableSql(operation.Table)).TrimEnd(';', '\r', '\n');

    builder.Append(sql).AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator).EndCommand();

    base.Generate(operation, model, builder, terminate);
}