在EF中正确使用IDatabaseInitializer是什么?

时间:2013-04-14 12:13:05

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

我有一个自定义的DatabaseInitialiser,位于

之下
/// <summary>
/// Implements the IDatabaseInitializer to provide a custom database initialisation for the context.
/// </summary>
/// <typeparam name="TContext">TContext is the DbContext</typeparam>
public class ParikshaDataBaseInitializer<TContext> : IDatabaseInitializer<TContext> where TContext : DbContext
{
    /// <summary>
    /// The method to Initialise the database.
    /// Takes care of the database cannot be dropped since it is in use problem while dropping and recreating the database.
    /// </summary>
    /// <param name="context">The DbContext on which to run the initialiser</param>
    public void InitializeDatabase(TContext context)
    {
        var exists = context.Database.Exists();

        try
        {
            if (exists && context.Database.CompatibleWithModel(true))
            {
                // everything is good , we are done
                return;
            }

            if (!exists)
            {
                context.Database.Create();
            }
        }
        catch (Exception)
        {
            //Something is wrong , either we could not locate the metadata or the model is not compatible.
            if (exists)
            {
                context.Database.ExecuteSqlCommand("ALTER DATABASE Pariksha SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
                context.Database.ExecuteSqlCommand("USE Master DROP DATABASE Pariksha");
                context.SaveChanges();
            }

            context.Database.Create();
        }
    } 
}
关于上述代码的内容不仅仅是hacky(随意提供帮助)

然后我添加了迁移并使迁移脚本也能正常工作。

    internal sealed class Configuration : DbMigrationsConfiguration<ParikshaContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
            ContextKey = "EFRepository.Context.ParikshaContext";
        }

        protected override void Seed(ParikshaContext context)
        {
        }
    }

迁移按预期工作。

现在,问题出在我的应用程序启动中,我该怎么办? 像这样的东西

 var config = new Configuration();
 var migrator = new DbMigrator(config);
 migrator.Update();

并且一些论坛在构造函数中也提到了这一点,这似乎有点奇怪,因为我不想在每次使用Context时检查db和schema是否正确。那么,这种技术的可能用途是什么?或者我是否认为该建议的背景是错误的?

public ParikshaContext() : base("Pariksha")
        {           
          Database.SetInitializer(new ParikshaDataBaseInitializer<ParikshaContext>());
        }

总结一下,

  1. 可用的不同技术的正确用例是什么?

  2. 什么是理想的策略,以便迁移在所有条件下工作,以及何时将数据库从一个环境移动到另一个环境?

2 个答案:

答案 0 :(得分:12)

这是我Db Initializer的尝试,它将Migration初始化程序和默认Db Create结合起来。 (注意:它不是理想的,更像是一个简单的练习,但是给出了你在这里要求的解决方案,主要是工作 - 只需检查我所做的所有更新)。

  

How to create initializer to create and migrate mysql database?

关于whyhow - 要完全理解我建议你也咨询EF source code(这是新版本,但在许多方面类似)

1)

a)Db初始化程序通常只被调用一次(每个连接) - 并且当您第一次尝试访问“模型”时(第一次查询或类似)。在初始化程序中放置一个断点进行检查。

所以把它放在构造函数中是完全安全的(虽然我更喜欢在某个地方启动它,配置也是如此)。 只有在需要初始化时才会调用(并且使用了last one set),您不应该手动调用它。

无论如何,要强制初始化程序,您可以执行this.Database.Initialize(force: true);

  

对于切换连接时,请参阅我关于问题的帖子   Code first custom connection string and migrations without using IDbContextFactory

b)如果您创建自己的IDatabaseInitializer并且仍希望迁移工作side by side

你不应该只从外面打电话给DbMigrator - 因为你的自定义初始化程序会错过整个'数据库创建'(例如,如果你想种子或其他东西 - 请查看我的示例以上)。

这两件事都是有效的'初始化者 - 所以你需要将它们整合到一个中,以某种方式'chain。请记住order of execution很重要(请参阅上面的问题) - 您应该检查“空状态”,然后调用DbMigrator,然后进行自己的初始化。我使用一个初始化程序作为基类,并合并另一个。

如果你只想seed - 你可以使用迁移配置,这是最简单的,如果合理的话。

2)

非常“开放式”并且没有单一答案。通常它有效,但是问题会被解决......

  • 迁移是3件事(我认为) - 您的代码模型/实体,您的数据库/表以及Db中的__MigrationHistory系统表。所有3个都需要留下in sync。如果您“不同步”,您可以删除迁移表,重新创建迁移(使用标记来保留现有数据库),然后继续前进 - 即有实时数据的解决方案。为此,请参阅How to ignore a table/class in EF 4.3 migrations

  • 移动数据库时需要删除/创建Db的权限,

  • 确保您的连接正确(更改配置 - 并与您的DbContext名称或ctor同步),

  • 保持简单,不做花哨的事情或从代码切换连接(可能但有问题)等,

  • 不要mix database / code个版本 - 即一个代码实体版本 - 一个数据库。如果您想要使用不同的代码版本(例如分期,制作)共享相同的Db - 请不要(EF6中提供多租户解决方案 - 例如this),

  • 如果您需要手动应用数据库 - 通过script生成Update-Database - 并应用它,请不要手动执行操作,否则会出错(迁移历史记录)表) - 见this one

......这只是为数不多的。它是相当稳定和可用的IMO - 但如果你遵守规则 - 并知道有什么限制。


class CreateAndMigrateDatabaseInitializer<TContext, TConfiguration> 
    : CreateDatabaseIfNotExists<TContext>, IDatabaseInitializer<TContext>
    where TContext : DbContext
    where TConfiguration : DbMigrationsConfiguration<TContext>, new()
{
    private readonly DbMigrationsConfiguration _configuration;
    public CreateAndMigrateDatabaseInitializer()
    {
        _configuration = new TConfiguration();
    }
    public CreateAndMigrateDatabaseInitializer(string connection)
    {
        Contract.Requires(!string.IsNullOrEmpty(connection), "connection");

        _configuration = new TConfiguration
        {
            TargetDatabase = new DbConnectionInfo(connection)
        };
    }
    void IDatabaseInitializer<TContext>.InitializeDatabase(TContext context)
    {
        var doseed = !context.Database.Exists();
        // && new DatabaseTableChecker().AnyModelTableExists(context);
        // check to see if to seed - we 'lack' the 'AnyModelTableExists'
        // ...could be copied/done otherwise if needed...

        var migrator = new DbMigrator(_configuration);
        // if (doseed || !context.Database.CompatibleWithModel(false))
        if (migrator.GetPendingMigrations().Any())
            migrator.Update();

        // move on with the 'CreateDatabaseIfNotExists' for the 'Seed'
        base.InitializeDatabase(context);
        if (doseed)
        {
            Seed(context);
            context.SaveChanges();
        }
    }
    protected override void Seed(TContext context)
    {
    }
}

答案 1 :(得分:4)

  

总结一下,

1) what is the correct use-case for the different techniques available ?

2) what would be the ideal strategy so that the migrations work in all conditions 
and when we move databases from one environment to another ?

退一步问自己何时希望EF进行初始化和迁移。在我的(企业)组织中,我们对如何迁移代码,数据和DDL有非常严格的规则,因此这些功能对我们没有任何价值。即使我们可以使用它们,我也会非常谨慎,直到我在不同的环境中拥有相当多的工具经验。

那么,为什么要使用它?您正在开始一个新的绿色领域项目,并且正在使用代码优先。现在,这是有用的地方。您可以编写代码,编译,运行,让EF担心新的或缺少的字段,关系和表。稍后,您可以在开发服务器中对其进行形式化,然后永远删除初始化程序和迁移程序。

至于如何设置?听起来你找到了一种有效的方法。我同意它似乎有点hacky,但代码类似于我的初始设置。我使用的是MVC.NET,所以我在Global.asax.cs中使用了以下内容:

 Database.SetInitializer(new DomainInitializer());

底线是我认为你的(2)是错误的问题。应该是,“我真的想这样做吗?”。你可能会这样做,但我会首先考虑其他更传统的选择,特别是如果你的公司超过10人。