EF6:编写你自己的code first 数据迁移操作(睡前来一篇,翻译的)
原英文版由EF团队成员 Rowan Miller 在2013年发表,此处只作翻译备忘。
数据迁移提供了一套强类型API,用于执行通用的操作,比如CreateIndex("dbo.Blogs","Url")
。同时,也提供了在一些特殊的情况下用户需要执行特殊SQL的接口,比如Sql("Grant Select On dbo.Blogs to guest);
。当然,这个SQL的接口也有一些缺点——那就是一旦你写了SQL那么就意谓着你的程序不再数据库无关了(比如ORACLE的语法和SQL SERVER有时候并不一样,这造成了不兼容)。
你可以把你需要的强类型API提交给ICECLOW。
创建我们自己的操作
我们打算添加一个允许我们将一张表的权限赋给一个用户的操作。正常情况下,我们希望写一个类似GrantPermission("dbo.Blogs","guest",Permission.Select);
的方法。
首先,我们创建一个MigrationOperation的子类,即一个自定义操作。除了实现IsDestructiveChange这个属性别的不需要任何操作。
using System.Data.Entity.Migrations.Model;
namespace ExtendingMigrations.Migrations
{
public enum Permission
{
Select,
Update,
Delete
}
public class GrantPermissionOperation : MigrationOperation
{
public GrantPermissionOperation(string table, string user, Permission permission)
: base(null)
{
Table = table;
User = user;
Permission = permission;
}
public string Table { get; private set; }
public string User { get; private set; }
public Permission Permission { get; private set; }
//是否为破坏性的修改
public override bool IsDestructiveChange
{
get { return false; }
}
}
}
下面,我们来写一个扩展方法以实现我们想要的功能。我们使用IDbMigration接口,这个接口让我们可以有权限操作DbMigration中不可见的API。(比如下面migration,本身没有AddOperation操作【fuck,这是怎么实现的!】,但是,强制转换为IDbMigration的时候就有了)
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
namespace ExtendingMigrations.Migrations
{
public static class Extensions
{
public static void GrantPermission(this DbMigration migration, string table, string user, Permission permission)
{
((IDbMigration)migration)
.AddOperation(new GrantPermissionOperation(table, user, permission));
}
}
}
你可以把上面的扩展方法作为普通的方法写在GrantPermissionOperation
类中,但是,假如像我这样使用了扩展方法,那么在类中我们就有更漂亮的写法。如下:
namespace ExtendingMigrations.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class GrantGuestPermissions : DbMigration
{
public override void Up()
{
this.GrantPermission("dbo.Blogs", "guest", Permission.Select);
}
public override void Down()
{
}
}
}
为我们的操作创建SQL
如果我们现在就调试运行我们的新的数据迁移类,那么肯定会出现异常。因为默认的SQL生成器不知道我们的操作流程。不过,我们可以从已经存在的migrator
(迁移器?可以这样叫么?)中继承然后将新的SQL生成逻辑添加到我们的操作中。
using System.Data.Entity.Migrations.Model;
using System.Data.Entity.Migrations.Sql;
namespace ExtendingMigrations.Migrations
{
public class MySqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(MigrationOperation migrationOperation)
{
var operation = migrationOperation as GrantPermissionOperation;
if (operation != null)
{
using (var writer = Writer())
{
writer.WriteLine(
"GRANT {0} ON {1} TO {2}",
operation.Permission.ToString().ToUpper(),
operation.Table,
operation.User);
Statement(writer);
}
}
}
}
}
注意上面的代码中,Generate
方法被我们重写了,但是,我们却没有执行base.Generate(..);
方法,因为父类的方法不知道如何处理这个逻辑,所以即使调用也会报异常。而且,也只有在我们默认的SQL生成器不知道如何处理我们自定义SQL的时候才会调用我们的Generate(MigrationOperation)
方法。
最后,我们需要在数据迁移配置(migrations configuration)中注册我们新的SQL生成器:
namespace ExtendingMigrations.Migrations
{
using System.Data.Entity.Migrations;
internal sealed class Configuration : DbMigrationsConfiguration<ExtendingMigrations.BloggingContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
// Register our custom generator
SetSqlGenerator("System.Data.SqlClient", new MySqlServerMigrationSqlGenerator());
}
}
}
测试
在我们VS的包管理控制台
中输入Update-Database -Script
,会输出更新数据库(对数据库进行数据迁移)时要执行的SQL脚本,其如下:
GRANT SELECT ON dbo.Blogs TO guest
INSERT [dbo].[__MigrationHistory]([MigrationId], [ContextKey], [Model], [ProductVersion])
VALUES ('201302272012532_GrantGuestPermissions', 'ExtendingMigrations.Migrations.Configuration', 0x1F8B0800000..., '6.0.0-alpha3-20222')