在EF中IDatabaseInitializer的正确用法是什么?

本文关键字:用法 是什么 EF IDatabaseInitializer | 更新日期: 2023-09-27 18:09:18

我有一个自定义的databaseinitializer

/// <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();
        }
    } 
}

上面的一些代码不仅仅是拙劣的(请随时提供帮助)

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

    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();

和一些论坛建议这在构造函数中似乎有点奇怪,因为我不想检查数据库和模式是否正确每次我使用上下文。那么,这个技巧的可能用途是什么呢,还是我把这个建议的上下文搞错了呢?

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

总结一下,

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

  2. 当我们将数据库从一个环境迁移到另一个环境时,理想的迁移策略是什么?

在EF中IDatabaseInitializer的正确用法是什么?

这是我在Db Initializer的尝试,它结合了Migration初始化器和默认的Db Create播种器。(注意:这不是理想的,更像是一个简单的练习,但给出了一个解决你在这里问的问题的方法,大部分工作-只要检查我所做的所有更新)。

如何创建初始化器来创建和迁移mysql数据库?

至于whyhow—为了充分理解,我建议您也参考EF源代码(这是新版本,但在许多方面相似)

1)

a) Db初始化器通常只调用一次(每个连接)-当你第一次尝试访问你的"模型"时(第一次查询或类似)。在初始化项中放置一个断点进行检查。

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

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

切换连接时,请参阅我的问题帖子
首先编写自定义连接字符串和迁移,不使用IDbContextFactory

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

你不应该只从外部调用DbMigrator -因为你的自定义初始化器会错过整个'db创建'(例如,如果你想要种子或其他东西-检查我上面的例子)。

这两个东西都是有效的"初始化器"-所以你需要将它们集成到一个中,这在某种程度上是chain的东西。记住order of execution很重要(参见上面的问题示例)-您应该检查"空条件",然后调用DbMigrator,然后进行自己的初始化。我使用一个初始化器作为基类,并合并另一个。

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

2)

是非常"开放的",没有单一的答案。通常它是有效的,但问题是预料之中的…

  • 迁移是3件事(正如我所看到的)-你的代码模型/实体,你的数据库/表,以及Db中的__MigrationHistory系统表。所有三个都需要保持in sync。如果你"不同步",你可以删除迁移表,重新创建迁移(带有保留现有数据库的标志),然后像以前一样继续前进——也就是说,当有实时数据时,有解决方案。请参阅如何在EF 4.3迁移中忽略表/类,

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

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

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

  • 不要mix database / code版本-即一个代码实体版本-一个数据库。如果你想与不同的代码版本共享相同的Db(例如,登台,生产)-不要(多租户解决方案将在EF6中可用-例如,

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

…这只是其中的几个例子。在我看来,它是相当稳定和可用的——但是如果你遵守规则——并且知道限制是什么。


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) 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担心新的或丢失的字段、关系和表。之后,您可以在开发服务器中形式化它,然后永远删除初始化器和迁移器。

至于如何设置呢?听起来你找到了有效的方法。我同意这看起来有点粗糙,但代码与我的初始设置相似。我正在使用MVC。因此,我在Global.asax.cs中使用了以下内容:

 Database.SetInitializer(new DomainInitializer());
最重要的是我认为你的(2)是一个错误的问题。应该是,"我真的想这样做吗?"结果可能是你需要,但我会先考虑其他更传统的选择,尤其是如果你在一家10人以上的公司。