问题描述
我正在尝试编写一个易于理解的 DBContext
类,该类采用自定义连接字符串,可以运行迁移,并且允许我使用包管理器生成迁移.
我好像在兜圈子.
我已经能够使用让我感觉很糟糕的代码让它工作.我在对这个关于连接字符串和迁移的问题的回答中记录了这一点.
Radek 的答案看起来比我的好得多,但我发现当我实施它然后尝试在包管理器中创建迁移时,我会收到消息
<块引用>目标上下文DataLayer.Context"不可构造.添加默认构造函数或提供 IDbContextFactory 的实现.
DataLayer.Context
是我的上下文类.
我不想提供 IDbContextFactory
的实现(而 Radek 的回答似乎表明它不需要)
更新:
如果我包含不带参数的构造函数,我可以生成迁移.例如
public Context() : base("ConnectionStringName") { }
为了创建上下文,我在 app.config 中传递连接字符串的名称
public Context(string connString) : base(connString){Database.SetInitializer(new CustomInitializer());数据库.初始化(真);}
最后我能够生成迁移,并为用户选择的数据库运行迁移.
但是:当我删除数据库然后运行我的应用程序时,我遇到了问题.
我正在使用的初始化代码,来自上面的链接是
公共类 CustomInitializer : IDatabaseInitializer;{公共无效初始化数据库(上下文上下文){尝试{if (!context.Database.Exists()){context.Database.Create();}别的{if (!context.Database.CompatibleWithModel(false)){var 配置 = 新配置();var 迁移器 = 新的 DbMigrator(配置);迁移器.Configuration.TargetDatabase =新的 DbConnectionInfo(context.Database.Connection.ConnectionString);IEnumerable<字符串>迁移 = migrator.GetPendingMigrations();foreach(迁移中的字符串迁移){var scriptor = new MigratorScriptingDecorator(migrator);字符串脚本 = scriptor.ScriptUpdate(null, 迁移);context.Database.ExecuteSqlCommand(脚本);}}}}捕捉(例外前){}}}
当我删除数据库时,会创建一个新数据库,但它没有表.那是因为我的表创建代码都在我的第一次迁移中.
所以 !context.Database.CompatibleWithModel(false)
条件内的代码不会运行.
然而,当它应该有元数据模型时,代码也没有第二次运行.
现在尝试运行迁移...
悲伤:到目前为止,Radek 的自定义初始化程序没有.
根据 NSGaga 的评论,如果我更改连接,我会退出应用程序.
var master = new myMDIForm();master.ConnectionType = 连接类型;//作为 app.config 中不同连接名称的枚举而(master.ConnectionType!= ConnectionType.None){应用程序.运行(主);}
(注意:我不知道您的自定义初始化程序,我只是看到了 - 这是连接缓存的通用解决方案
问题和迁移初始化程序 - 但应该使用变体)
这对我有用 - 并且基于我描述的错误行为 这里.
静态字符串_connection;公共 MyContext(): 基地(_connection ?? "DefaultConection"){Database.SetInitializer(new MigrateDatabaseToLatestVersion());}public MyContext(字符串连接):基地(连接){_connection = 连接;Database.SetInitializer(new MigrateDatabaseToLatestVersion());}
几点:
1) 在您的上下文中定义 static
连接 - 并在设置新连接"时将其更改为保存 最新值
.这有助于保持同步——无论谁访问 DbContext 都有相同的(它在概念上与 Factory
相似,只是在视觉上更容易,
2) Migrations
的问题是 - MigrateDatabaseToLatestVersion
在内部将连接(以及内部的整个配置)缓存在 readonly
字段中- 即使您在 DbContext
或您在外部设置的任何内容中更改它,它也会不同步".
除了制作一个新的初始化程序"之外,没有办法解决这个问题.
3) @Radek 发现的实际上是该问题的解决方案 - 并且符合 (2).我刚刚删除了 Initialize(true)
因为它是不必要的 - 当时间合适"时会调用它.
就是这样.
有了这个,我现在可以全天候 flip
我的连接 - 随心所欲.
(这意味着我可以在 runtime
更改连接 - 迁移/创建两个或更多数据库并不断更改连接并同时处理它们)
这是我实际用于在连接之间循环的代码......
for (var flip = true; true; flip = !flip){使用 (var db = new MyContext(flip ? "Name=DefaultConnection" : "Name=OtherConnection")){//通常的数据库代码}}
<块引用>
关键是每次设置连接时都要设置初始化器
.
旧的仍然存在 - 旧的连接(您不能在应用程序运行时删除 Db)
I am trying to write an easy to understand DBContext
class that takes a custom connection string, can run migrations, and that I allows me to generate migrations using Package Manager.
I seem to be going around in circles.
I have been able to get it working using code that feels awful to me. I documented this in my answer to This question on connection string and migrations.
Radek's answer looks much better than mine, but I find that when I implement it and then try and create a migration in Package Manager I get the message
The target context 'DataLayer.Context' is not constructible. Add a default constructor or provide an implementation of IDbContextFactory.
Where DataLayer.Context
is my context class.
I don't want to provide an implementation of IDbContextFactory
( and Radek's answer seems to indicate it isn't needed )
UPDATE:
I can generate a migration if I include a constructor with no parameter. For example
public Context() : base("ConnectionStringName") { }
For my context creation I pass the name of the connection string in app.config
public Context(string connString) : base(connString)
{
Database.SetInitializer(new CustomInitializer());
Database.Initialize(true);
}
At last I am able to both generate migrations, and run migrations for databases that the user selects.
HOWEVER: When I drop the database and then run my app I have problems.
The initialiser code I am using, from the link above is
public class CustomInitializer : IDatabaseInitializer<Context>
{
public void InitializeDatabase(Context context)
{
try
{
if (!context.Database.Exists())
{
context.Database.Create();
}
else
{
if (!context.Database.CompatibleWithModel(false))
{
var configuration = new Configuration();
var migrator = new DbMigrator(configuration);
migrator.Configuration.TargetDatabase =
new DbConnectionInfo(context.Database.Connection.ConnectionString);
IEnumerable<string> migrations = migrator.GetPendingMigrations();
foreach (string migration in migrations)
{
var scriptor = new MigratorScriptingDecorator(migrator);
string script = scriptor.ScriptUpdate(null, migration);
context.Database.ExecuteSqlCommand(script);
}
}
}
}
catch (Exception ex)
{
}
}
}
When I drop the database a new one gets created but it has no tables. That would be because my table creation code is all in my first migration.
So the code inside the !context.Database.CompatibleWithModel(false)
condition does not run.
However alas, the code also does not run the second time around when it should have the metadatamodel.
Now to try and get the migration running...
SADNESS: No with Radek's custom initializer so far.
From NSGaga's comments I have resorted to exiting the application if I change connection.
var master = new myMDIForm();
master.ConnectionType = connectionType; // being an enum of the different connection names in app.config
while (master.ConnectionType != ConnectionType.None )
{
Application.Run(master);
}
(note: I have no idea about your custom initializer, I just saw that - this is a general solution to the connection caching
problem and migration initializer - but should work with variations)
This is what works for me - and based on the buggy behavior that I described here.
static string _connection;
public MyContext()
: base(_connection ?? "DefaultConection")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyNamespace.Migrations.Configuration>());
}
public MyContext(string connection)
: base(connection)
{
_connection = connection;
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyNamespace.Migrations.Configuration>());
}
Few points:
1) Define static
connection in your context - and as you set a 'new one' change it to hold the latest value
. That helps keeping things in synchronization - no matter who accesses the DbContext has the same one (it's similar in concept to Factory
just easier on the eyes,
2) Problem with Migrations
is - that MigrateDatabaseToLatestVersion
caches the connection (and entire configuration internally) in a readonly
field internally - and even if you change it in your DbContext
, or whatever you set outside, it gets 'out of sync`.
There is no way getting around that but to make a 'new initializer'.
3) What @Radek discovered was actually the solution for that problem - and in line with (2). I just removed the Initialize(true)
as it's unnecessary - that gets called when 'the time is right'.
That's about it.
With this I can now flip
my connections around the clock - and as I want.
(meaning I can change connection at runtime
- migrate/create two, or more, databases and keep changing connections and working on them simultaneously)
And this is the code I actually use to cycle between connections...
for (var flip = true; true; flip = !flip)
{
using (var db = new MyContext(flip ? "Name=DefaultConnection" : "Name=OtherConnection"))
{
// usual db code
}
}
The key is to
set initializer each time you set the connection
.
The old one is still around - and the old connection (you can't delete the Db while app is running)
这篇关于在不使用 IDbContextFactory 的情况下编写第一个自定义连接字符串和迁移的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!