如何使用自定义配置步骤配置EF数据库?

时间:2013-11-21 03:13:08

标签: entity-framework ef-code-first code-first ef-migrations entity-framework-6

我们在一个相对较新的项目中使用EF 6 Code First Migrations(即没有太多的混乱可以解决)。此外,由于这是一个“enterprise-y”应用程序,因此我们为目标数据库提供了一些特定的部署规则:

  • 所有应用程序级数据访问必须通过特定的DB用户(app-user
  • 完成
  • app-user无权创建新数据库

因此,为了为此应用程序正确配置新的目标数据库,我们需要:

  • CREATE DATABASE [database_name] CONTAINMENT = PARTIAL
  • CREATE USER [app-user] WITH PASSWORD=N'p@ssw0rd'
  • (另外为此新用户分配特定的DB角色)

我希望通过编写自定义IDatabaseInitializer<TContext>来实现这一点,但似乎我无法在正确的位置挂钩数据库初始化。

从概念上讲,我想这样做:

  • 有一个连接字符串,用于使用“controller”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?如果是这样,我做错了什么?

非常感谢。

2 个答案:

答案 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;
    }