EF中IDatabaseInitializer的正确使用是什么? [英] What is the correct use of IDatabaseInitializer in EF?

查看:780
本文介绍了EF中IDatabaseInitializer的正确使用是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  ///< summary> 
///实现IDatabaseInitializer,为上下文提供自定义数据库初始化。
///< / summary>
///< typeparam name =TContext> TContext是DbContext< / typeparam>
public class ParikshaDataBaseInitializer< TContext> :IDatabaseInitializer< TContext>其中TContext:DbContext
{
///< summary>
///初始化数据库的方法。
///注意数据库无法删除,因为它在删除和重新创建数据库时正在使用中。
///< / summary>
///< param name =context>运行初始化器< / param>的DbContext
public void InitializeDatabase(TContext context)
{
var exists = context.Database.Exists();

try
{
if(exists&&&&context.Database.CompatibleWithModel(true))
{
//一切都很好,我们完成
返回;
}

if(!exists)
{
context.Database.Create();
}
}
catch(异常)
{
//有些错误,我们找不到元数据或模型不兼容。
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(随意帮助)



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

 内部密封类配置: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();

一些论坛在构造函数中也提出了这样的建议,似乎有点奇怪,因为我不想以检查每次使用上下文时db和schema是否正确。那么可能使用这种技术或者是否将建议的上下文看作是错误的?

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

总而言之,


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


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



解决方案

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


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


对于为什么如何 - 完全明白我建议您也参考 EF源代码(这是新版本,但在许多方面相似)



< h3> 1)

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



所以把它放在构造函数中是完全安全的(虽然我喜欢它在启动时也是配置)。 只有在需要初始化(并且最后一个被使用)时才会调用它,您不应手动调用它。



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


当切换连接时看到我的问题发布

不使用IDbContextFactory的代码第一个自定义连接字符串和迁移


b)如果您创建自己的 IDatabaseInitializer ,而您仍然希望将迁移工作并行



你不应该只是调用 DbMigrator 从外部 - 由于您的自定义初始化程序将错过整个数据库创建(例如,如果你想种子或东西 - 检查我的示例上面)。



这两件事情都是有效的初始化器 - 所以你需要将它们整合到一个中,这将链条某些东西。请记住,执行顺序是重要的(请参见上面的问题) - 您应该检查'空条件',然后调用 DbMigrator ,然后做自己的初始化。我使用一个初始化器作为基类,并合并另一个。



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



2)



很开放,没有一个答案。通常它有效,但问题出现...




  • 迁移是3件事(正如我所见) - 你的代码模型/实体,数据库/表以及Db中的 __ MigrationHistory 系统表。所有3需要保持同步。如果您不同步,则可以删除迁移表,重新创建迁移(使用一个标志来保留现有的数据库),然后按照以前的步骤进行操作 - 即使用实时数据来解决问题。为此,请参阅如何忽略EF 4.3迁移中的表/类


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


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


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


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


  • 如果需要手动应用数据库 - 通过更新数据库生成脚本并应用那不要手动做事情,否则会出错(迁移历史记录表) - 请参阅这一个




...这仅仅是为了少数几个。这是非常稳定和可用的IMO - 但如果您遵守规则 - 并且知道什么是限制。






 类CreateAndMigrateDatabaseInitializer< TContext,TConfiguration> 
:CreateDatabaseIfNotExists< TContext>,IDatabaseInitializer< TContext>
其中TContext:DbContext
其中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();
//&&&新的DatabaseTableChecker()。AnyModelTableExists(context);
//检查是否种子 - 我们缺少'AnyModelTableExists'
// ...可以复制/完成,否则需要...

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

//继续使用'Seed'的$ CreateDatabaseIfNotExists'
base.InitializeDatabase(context);
if(doseed)
{
种子(上下文);
context.SaveChanges();
}
}
protected override void Seed(TContext context)
{
}
}
pre>

I have a custom DatabaseInitialiser which is below

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

something about the above code is more than just hacky (Feel free to chip in with help)

I then added migrations and got the migration script to work correctly as well.

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

        protected override void Seed(ParikshaContext context)
        {
        }
    }

the migrations work as expected.

Now, the questions is in my application startup what should I do ? Something like this

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

and some forums suggested this as well in the constructor which seems a little odd because I don't want to check if the db and schema are correct everytime I use the Context. So, what could be the possible use of this technique or did I get the context of the suggestion as wrong ?

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

To summarise,

  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 ?

解决方案

This is my attempt at Db Initializer which combines both Migration initializer and the default Db Create one with seeding. (note: it's not ideal, more like a simple exercise but gives a solution to what you're asking here, mostly works - just check all the updates I made).

How to create initializer to create and migrate mysql database?

As for the why and how - to fully understand that I'd suggest you also consult the EF source code (that's new version but in many ways similar)

1)

a) Db initializer gets called usually only once (per connection) - and when you try to access your 'model' for the first time (first query or similar). Put a breakpoint in your initializer to check.

So it's completely safe to put it within constructor (though I prefer it on startup somewhere, config also). It only gets called when it's needed to initialize (and the last one set is used), you shouldn't invoke it manually.

Anyhow, to force the initializer you can do the this.Database.Initialize(force: true);

For when switching connections see my post on problems
Code first custom connection string and migrations without using IDbContextFactory

b) If you create your own IDatabaseInitializer and you still want migrations to work side by side

You shouldn't just call DbMigrator from outside - as your custom initializer will miss on the whole 'db creation' (e.g. if you wish to seed or something - check my example above).

Both things are effectively 'initializers' - so you'd need to integrate them into one, that'd chain things in some way. Have in mind that order of execution is important (see above ex. for problems) - you should check for 'empty condition', then call DbMigrator, then do your own intialization. I used one initializer as a base class, and merged the other.

And if you just want to seed - you can use the migration Configuration, that's the simplest if plausible.

2)

Is pretty 'open ended' and there is no single answer. Usually it works, but issues are expexted...

  • Migrations are 3 things (as I see it) - your code model/entities, your database/tables, and the __MigrationHistory system table in the Db. All 3 need to stay in sync. If you get 'out of sync', you can drop the migration table, recreate migrations (with a flag to keep existing db) and then move on as before - i.e. there are solutions for when with live data. For this see How to ignore a table/class in EF 4.3 migrations,

  • you'd need permissions to drop/create Db for when moving database,

  • make sure your connection is right (change in config - and in sync with your DbContext name or ctor),

  • keep it simple, don't do fancy things or switch connections from code (possible but has problems) etc.,

  • don't mix database / code versions - i.e. one code entities version - one database. If you want to share the same Db with different code versions (e.g. staging, production) - don't (multi-tenant solutions will be available in EF6 - e.g. this),

  • if you need to manually apply the database - generate the script via Update-Database - and apply that, don't do things manually or you'll get it wrong (the migration history table) - see this one,

...that's just to name the few. It is pretty stable and usable IMO - but if you follow the rules - and know what the limitations are.


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)
    {
    }
}

这篇关于EF中IDatabaseInitializer的正确使用是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆