如何Database.SetInitializer实际工作? (EF代码首先创建数据库,并使用多

2019-08-19 18:26发布

我想写创建一个数据库,并在其上运行迁移的方法,给定的连接字符串。

我需要多个连接,因为我在一个单独的数据库中记录的审核日志。 我用这样的代码获取连接字符串出的app.config的

ConfigurationManager.ConnectionStrings["Master"].ConnectionString;

代码工作在我的app.config而不是其他定义的第一个连接字符串,这使我想到,不知怎的,它是获得连接字符串从app.config中以某种方式,我不知道。

我的代码来创建数据库,如果它不存在,是

private static Context MyCreateContext(string ConnectionString)
  {
   // put the connection string where the factory method can get it
   AppDomain.CurrentDomain.SetData("ConnectionString", ConnectionString );
   var factory = new ContextFactory();
   // I know I need this line - but I cant see how what follows actually uses it
   Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context,DataLayer.Migrations.Configuration>());
   var context = factory.Create();
   context.Database.CreateIfNotExists(); 
   return context
   }

在Migrations.Configuration的代码

Public sealed class Configuration :  DbMigrationsConfiguration<DataLayer.Context>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }
}

上下文工厂代码

  public class ContextFactory : IDbContextFactory<Context>
{
    public Context Create()
    {
        var s = (string)AppDomain.CurrentDomain.GetData("ConnectionString");

        return new Context(s);
    }
}

因此,我创建上下文之前设置连接字符串。
我在哪里可以是想错了,因为在连接字符串之外的所有数据库名称相同,迁移代码与一个连接字符串运行,但犯规和别人跑?

我不知道如果我的问题是如何理解如何Database.SetInitializer实际工作要做。 我猜一些关于反射或仿制药。 如何使调用SetInitializer领带扎到我的实际情境?

我曾尝试下面的代码,但迁移不会运行

 private static Context MyCreateContext(string ConnectionString)
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context, DataLayer.Migrations.Configuration>());
        var context = new Context(ConnectionString);
        context.Database.CreateIfNotExists();
    }

这个问题似乎是相关的

更新:

我能得到的迁移工作,如果我是指利用公共MyContext()的连接字符串:基地(“MyContextConnection”) - 它指向的配置

我也能得到迁移工作使用上下文的不同的情况下,如果我创建了一个ContextFactory类和通过引用全球与它的连接。 (见我的回答对相关问题的链接)

现在我想知道为什么它要这么辛苦。

Answer 1:

I'm not sure exactly as to what the problems are you facing, but let me try

The easiest way to provide connection - and be sure it works that way...

1) Use your 'DbContext' class name - and define a connection in the app.config (or web.config). That's easiest, you should have a connection there that matches your context class name,

2) If you put it into the DbContext via constructor - then be consistent and use that one. I'd also suggest to 'read' from config connections - and again name it 'the same' as your context class (use the connection 'name', not the actual string),

3) if none is present - EF/CF makes the 'default' one - based on your provider - and your context's class name - which usually isn't what you want,

You shouldn't customize with initializers for that reason - initializers should be agnostic and serve other purpose - setup connection in the .config - or directly on your DbContext

Also check this Entity Framework Code First - How do I tell my app to NOW use the production database once development is complete instead of creating a local db?

Always check 'where your data' goes - before doing anything.

For how the initializer actually works - check this other post of mine, I made a thorough example

How to create initializer to create and migrate mysql database?

Notes: (from the comments)

Connection shouldn't be very dynamic - config is the right place for it to be, unless you have a good reason.
Constructor should work fine too.
CreateDbIfNotExists doesn't work well together with the 'migration' initializer. You can just use the MigrateDatabaseToLatestVersion initializer. Don't 'mix' it

Or - put something like public MyContext() : base("MyContextConnection") - which points to <connectionStrings> in the config

To point to connection - just use its 'name' and put that into constructor.

Or use somehting like ConfigurationManager.ConnectionStrings["CommentsContext"].ConnectionString

Regarding entertaining 'multiple databases' with migrations (local and remote from one app) - not exactly related - but this link - Migration not working as I wish... Asp.net EntityFramework

Update: (further discussion here - Is adding a class that inherits from something a violation of the solid principles if it changes the behavior of code?)

It is getting interesting here. I did manage to reproduce the problems you're facing actually. Here is a short breakdown on what I think it's happening:

First, this worked 'happily':

Database.SetInitializer(new CreateAndMigrateDatabaseInitializer<MyContext, MyProject.Migrations.Configuration>());
for (var flip = false; true; flip = !flip)
{
    using (var db = new MyContext(flip ? "Name=MyContext" : "Name=OtherContext"))
    {
        // insert some records...
        db.SaveChanges();
    }
}

(I used custom initializer from my other post, which controls migration/creation 'manually')

That worked fine w/o an Initializer. Once I switched that on, I ran into some curious problems.

I deleted Db-s (two, for each connection). I expected to either not work, or create one db, then another in the next pass (like it did, w/o migrations, just 'Create' initializer).

What happened, to my surprise - is it actually created both databases on the first pass ??

Then, being a curious person:), I put breakpoints on the MyContext ctor, and debugged through the migrator/initializer. Again empty/no db-s etc.

It created first instance on my call within the flip. Then on the first access to 'model', it invoked the initializer. Migrator took over (having had no db-s). During the migrator.Update(); it actually constructs the MyContext (I'm guessing via generic param in Configuration) - and calls the 'default' empty ctor. That had the 'other connection/name' by default - and creates the other Db all as well.

So, I think this explains what you're experiencing. And why you had to create the 'Factory' to support the Context creation. That seems to be the only way. And setting some 'AppDomain' wide 'connection string' (which you did well actually) which isn't 'overriden' by default ctor call.

解决方案,我看到的是 - 你只需要运行通过工厂的一切 - 在有“翻转”的连接(无需静态连接,只要你的工厂是一个单。



Answer 2:

您可以在MigrateDatabaseToLatestVersion构造提供的配置。 如果在设定的DbContext初始化,您还可以通过“真正”使用当前的连接字符串。



文章来源: How does Database.SetInitializer actually work? (EF code-first create database and apply migrations using several connection strings)