我们在一个相对较新的项目中使用EF 6 Code First Migrations(即没有太多的混乱可以解决)。此外,由于这是一个“enterprise-y”应用程序,因此我们为目标数据库提供了一些特定的部署规则:
app-user
)app-user
无权创建新数据库因此,为了为此应用程序正确配置新的目标数据库,我们需要:
CREATE DATABASE [database_name] CONTAINMENT = PARTIAL
CREATE USER [app-user] WITH PASSWORD=N'p@ssw0rd'
我希望通过编写自定义IDatabaseInitializer<TContext>
来实现这一点,但似乎我无法在正确的位置挂钩数据库初始化。
从概念上讲,我想这样做:
app-user
user 我尝试使用的代码看起来有点像这样:
internal class ProvisionThenMigrateInitializer<TContext, TConfiguration>
: MigrateDatabaseToLatestVersion<TContext, TConfiguration>, IDatabaseInitializer<TContext>
where TContext : DbContext
where TConfiguration : DbMigrationsConfiguration<TContext>, new()
{
private readonly DbMigrationsConfiguration _readWriteConfiguration;
private readonly string _provisioningConnectionName;
public ProvisionThenMigrateInitializer(string readWriteConnectionName, string provisioningConnectionName)
{
_provisioningConnectionName = provisioningConnectionName;
_readWriteConfiguration = new TConfiguration
{
TargetDatabase = new DbConnectionInfo(readWriteConnectionName)
};
}
void IDatabaseInitializer<TContext>.InitializeDatabase(TContext context)
{
if (context.Database.Exists())
{
if (!context.Database.CompatibleWithModel(false))
{
DbMigrator migrator = new DbMigrator(_readWriteConfiguration);
migrator.Update();
}
}
else
{
// TODO - Create the DB and user here...
string[] sqlStatements =
{
"CREATE DATABASE [database_name] CONTAINMENT = PARTIAL ",
"USE [database_name]",
"CREATE USER [app_user] WITH PASSWORD=N'p@ssw0rd'",
"USE [database_name]",
"ALTER ROLE [db_datareader] ADD MEMBER [app_user]",
"ALTER ROLE [db_datawriter] ADD MEMBER [app_user]",
};
string connectionString = ConfigurationManager.ConnectionStrings[_provisioningConnectionName].ConnectionString;
SqlConnection sqlConnection = new SqlConnection(connectionString);
foreach (SqlCommand command in sqlStatements.Select(sqlStatement => new SqlCommand(sqlStatement, sqlConnection)))
{
command.ExecuteNonQuery();
}
context.Database.Create();
Seed(context);
context.SaveChanges();
}
}
我将初始化程序设置为在DbContext
派生类的静态构造函数中使用:
Database.SetInitializer(new ProvisionThenMigrateInitializer<Context, Configuration>(
DOMAIN_MODEL_CONNECTION_STRING_NAME,
DOMAIN_MODEL_PROVISIONING_CONNECTION_STRING_NAME));
但是,当我尝试使用我喜欢的新自定义数据库初始化程序时,按照以下方式,它只是简单的不起作用:
using (Context c = new Context())
{
try
{
c.Database.Initialize(true);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
我认为,当我尝试调用c.Database.Initialize(true)
时,EF已尝试连接到数据库(使用app_user
凭据而不是“配置凭据”,连接尝试失败,并且我们炸掉了。
实际上是否可以以允许我的数据库配置的方式使用EF 6,Code First和Migrations?如果是这样,我做错了什么?
非常感谢。
答案 0 :(得分:1)
我是这样做的:
我有一个'admin'SQL登录,它是'dbcreator'和'securityadmin'固定服务器角色的成员。
我有两个连接字符串:一个指定'admin'sql登录,另一个指定我保留供租户连接到db的sql登录名。 “租户”登录通过初始迁移创建,并且仅授予对域模型数据库的读取和写入访问权。
我有一个域名模型。 我有我的DbContext类。 我的DbContext类上有一个无参数构造函数,它指定ADMIN连接字符串,用于运行迁移;我有另一个构造函数,它指定TENANT连接字符串,并且是通过代码使用的构造函数,用于在登录的租户用户的上下文中进行的所有数据库访问。 公共语境() :base(ADMIN_CONNECTION_STRING_NAME) { //等等 和 public Context(int tenantOrgId) :base(TENANT_CONNECTION_STRING_NAME) {
在启用迁移之前,我在单元测试中使用了DbContext,这导致EF Code First创建了DB目录。 我已经启用了已经产生初始DbMigration的迁移。
然后我编辑了最初的DbMigration“Up”方法来配置租户sql登录并将其成为读者和作者角色的成员资格:
public override void Up()
{
SqlConnectionStringBuilder domainModelConnectionStringBuilder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings[Context.TENANT_CONNECTION_STRING_NAME].ConnectionString);
string domainModelDatabaseName = domainModelConnectionStringBuilder.InitialCatalog;
Sql(string.Format("IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = 'gsp_domainmodel_tenant') CREATE LOGIN [gsp_domainmodel_tenant] WITH PASSWORD=N'ge0sp@tia!', DEFAULT_DATABASE=[{0}], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF", domainModelDatabaseName));
Sql(string.Format("USE [{0}]", domainModelDatabaseName));
Sql(string.Format("IF NOT EXISTS (SELECT * FROM [{0}].sys.database_principals WHERE name = 'gsp_domainmodel_tenant') CREATE USER [gsp_domainmodel_tenant] FOR LOGIN [gsp_domainmodel_tenant] WITH DEFAULT_SCHEMA=[gsp]", domainModelDatabaseName));
Sql(string.Format("USE [{0}]", domainModelDatabaseName));
Sql(string.Format("ALTER ROLE [db_datareader] ADD MEMBER [gsp_domainmodel_tenant]", domainModelDatabaseName));
Sql(string.Format("USE [{0}]", domainModelDatabaseName));
Sql(string.Format("ALTER ROLE [db_datawriter] ADD MEMBER [gsp_domainmodel_tenant]", domainModelDatabaseName));
CreateTable( //etc
如果您对使用Update-Database将迁移应用到其本地数据库的团队感到满意,那么您就需要做的就是,并且您对在命令行上执行Migrate.exe以在您的部署上部署数据库感到满意构建机器,您很高兴使用自己的智能将db更改部署到生产中。
您可以更进一步,指定MigrateDatabaseToLatestVersion初始化程序,以在本地开发工作站和部署到的环境上自动部署迁移。 技巧是您需要使用无参数DbContext构造函数运行MigrateDatabaseToLatestVersion初始化程序,以便在ADMIN sql登录(而不是TENANT)的上下文中应用迁移。这样就实现了: 静态上下文() { Database.SetInitializer(new MigrateDatabaseToLatestVersion());
// Make the initializer run now, with the parameterless constructor, such that the migrations are run using the admin connection string.
using(var initializerCtx = new Context())
{
initializerCtx.Database.Initialize(true);
}
}
答案 1 :(得分:0)
你应该能够做你想做的事。问题的关键是确保使用正确的连接细节访问/更新上下文。
在代码中调用适合您的迁移方法。
更改MigrateDatabaseToLatestVersion
以符合您的迁移策略。
编辑:我将尝试总结这个想法并显示一个片段样本。
基本上我使用默认为DONT TOUCH DB的LUW类。 Luw需要构造函数中的DBServer和DBName 我有一个获取SQL Server的DBConnection的工具
从管理员我有一个按钮。迁移。 然后,我可以在适合时触发自动迁移。 我目前使用自动。但是这个概念可以很好地应用于托管迁移。
public class Luw { public Luw(string dataSource,string dbName){//构造函数 Context = GetContext(dataSource,dbName); }
public override void MigrateDb() {
// i put this method in my UoW class, I trigger Migrations when I want them to start.
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MYDbContext, MYSECIALMigrationConfiguration>());
// Context = GetDefaultContext(); //HERE GET THE CONTEXT WITH CORRECT CONNECTION INFO
Context.Database.Initialize(true);
}
public static MyDbContext GetContext(string dataSource, string dbName)
{
Database.SetInitializer(new ContextInitializerNone<MyDbContext>());
return new MyDbContext((MYTOOLS.GetSQLConn4DBName(dataSource,dbName )),true);
}
public class MYSPECIALMigrationConfiguration : MYBaseMigrationConfiguration<MYDbContext>{ }
public abstract class MYBaseMigrationConfiguration<TContext> : DbMigrationsConfiguration<TContext>
where TContext : DbContext{
protected MYBaseMigrationConfiguration() {
AutomaticMigrationsEnabled = true; // you can still chnage this later if you do so before triggering Update
AutomaticMigrationDataLossAllowed = true; // you can still chnage this later if you do so before triggering Update
}
public clas SQLTOOLS{
// ..... for SQL server....
public DbConnection GetSqlConn4DbName(string dataSource, string dbName) {
var sqlConnStringBuilder = new SqlConnectionStringBuilder();
sqlConnStringBuilder.DataSource = String.IsNullOrEmpty(dataSource) ? DefaultDataSource : dataSource;
sqlConnStringBuilder.IntegratedSecurity = true;
sqlConnStringBuilder.MultipleActiveResultSets = true;
var sqlConnFact = new SqlConnectionFactory(sqlConnStringBuilder.ConnectionString);
var sqlConn = sqlConnFact.CreateConnection(dbName);
return sqlConn;
}