EntityFramework code-первая пользовательская строка подключения и миграции
Когда я создаю контекст со строкой соединения по умолчанию (как читается из app.config
), База данных создается и миграции работают - в основном все в порядке. В то время как при программном создании строки подключения (с помощью SqlConnectionStringBuilder
):
- база данных не создается, когда ее нет (сценарий
A
); -
CreateDbIfNotExists()
создает самую новую версию модели базы данных, но механизмы миграции не вызываются (сценарийB
).
В A
возникает исключение, когда я хочу получить доступ к базе данных, так как - очевидно - ее там нет. В B
база данных создается правильно, механизмы миграции не вызываются, как это имеет место в стандартной строке подключения.
Приложение.config :
"Data Source=localhost\SQLEXPRESS;Initial Catalog=Db13;User ID=xxx;Password=xxx
"
Строитель :
sqlBuilder.DataSource = x.DbHost;
sqlBuilder.InitialCatalog = x.DbName;
sqlBuilder.UserID = x.DbUser;
sqlBuilder.Password = x.DbPassword;
Инициализатор :
Database.SetInitializer(
new MigrateDatabaseToLatestVersion<
MyContext,
Migrations.Configuration
>()
);
Технические характеристики : Entity Framework: 5.0, DB: SQL Server Express 2008
7 ответов:
Если ваша миграция не работает правильно, попробуйте установить
Database.Initialize(true)
в DbContext ctor.public CustomContext(DbConnection connection) : base(connection, true) { Database.Initialize(true); }
У меня аналогичная проблема с миграциями. И в моем решении я должен всегда устанавливать инициализатор базы данных в ctor, как показано ниже
public CustomContext(DbConnection connection) : base(connection, true) { Database.SetInitializer(new CustomInitializer()); Database.Initialize(true); }
В пользовательском инициализаторе вы должны реализовать метод
InitalizeDatabase(CustomContex context)
, например.class CustomInitializer : IDatabaseInitializer<CustomContext> { public void InitializeDatabase(CustomContext context) { if (!context.Database.Exists || !context.Database.CompatibleWithModel(false)) { var configuration = new Configuration(); var migrator = new DbMigrator(configuration); migrator.Configuration.TargetDatabase = new DbConnectionInfo(context.Database.Connection.ConnectionString, "System.Data.SqlClient"); var migrations = migrator.GetPendingMigrations(); if (migrations.Any()) { var scriptor = new MigratorScriptingDecorator(migrator); string script = scriptor.ScriptUpdate(null, migrations.Last()); if (!String.IsNullOrEmpty(script)) { context.Database.ExecuteSqlCommand(script); } } } } }
Обновлено
Он является решением, с нет строки подключения в приложении.конфиг. Использует автоматическую миграцию и 2 базы данных, использующие один и тот же контекст. Реальная среда выполнения поставляла соединение. Подход.
Приложение.CONFIG (использует EF 6)
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"> <parameters> <parameter value="Data Source=localhost; Integrated Security=True; MultipleActiveResultSets=True" /> </parameters> </defaultConnectionFactory> </entityFramework> </configuration>
Я переписал код, чтобы сделать как можно меньше для демо:
using System; using System.Data.Common; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Entity.Migrations; namespace Ef6Test { public class Program { public static void Main(string[] args) { Database.SetInitializer(new MigrateDatabaseToLatestVersion<Ef6Ctx, Ef6MigConf>()); WhichDb.DbName = "HACKDB1"; var sqlConn = GetSqlConn4DBName(WhichDb.DbName); var context = new Ef6Ctx(sqlConn); context.Database.Initialize(true); AddJunk(context); //sqlConn.Close(); //?? whatever other considerations, dispose of context etc... Database.SetInitializer(new MigrateDatabaseToLatestVersion<Ef6Ctx, Ef6MigConf>()); // yes its default again reset this !!!! WhichDb.DbName = "HACKDB2"; var sqlConn2 = GetSqlConn4DBName(WhichDb.DbName); var context2 = new Ef6Ctx(sqlConn2); context2.Database.Initialize(true); AddJunk(context2); } public static class WhichDb { // used during migration to know which connection to build public static string DbName { get; set; } } private static void AddJunk(DbContext context) { var poco = new pocotest(); poco.f1 = DateTime.Now.ToString(); // poco.f2 = "Did somebody step on a duck?"; //comment in for second run context.Set<pocotest>().Add(poco); context.SaveChanges(); } public static DbConnection GetSqlConn4DBName(string dbName) { var sqlConnFact = new SqlConnectionFactory( "Data Source=localhost; Integrated Security=True; MultipleActiveResultSets=True"); var sqlConn = sqlConnFact.CreateConnection(dbName); return sqlConn; } } public class MigrationsContextFactory : IDbContextFactory<Ef6Ctx> { public Ef6Ctx Create() { var sqlConn = Program.GetSqlConn4DBName(Program.WhichDb.DbName); // NASTY but it works return new Ef6Ctx(sqlConn); } } public class Ef6MigConf : DbMigrationsConfiguration<Ef6Ctx> { public Ef6MigConf() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } } public class pocotest { public int Id { get; set; } public string f1 { get; set; } // public string f2 { get; set; } // comment in for second run } public class Ef6Ctx : DbContext { public DbSet<pocotest> poco1s { get; set; } public Ef6Ctx(DbConnection dbConn) : base(dbConn, true) { } } }
Я смог переключаться между соединениями, используя следующую технику
1) иметь несколько имен строк подключения, определенных в приложении.конфиг.
2) иметь конструктор в контексте, который принимает имя строки соединения
public Context(string connStringName) : base(connStringName) { }
3) настройте метод Create для контекста - и сделайте его способным получать имя соединения (используя немного трюка )
public class ContextFactory : IDbContextFactory<Context> { public Context Create() { var s = (string)AppDomain.CurrentDomain.GetData("ConnectionStringName"); var context = new Context(s); return context; } }
4) моя конфигурация миграции ....
public sealed class Configuration : DbMigrationsConfiguration<SBD.Syrius.DataLayer.Context> { etc }
5) настройка функции для создания контекст.
private static Context MyCreateContext(string connectionStringName ) { // so that we can get the connection string name to the context create method AppDomain.CurrentDomain.SetData("ConnectionStringName", connectionStringName); // hook up the Migrations configuration Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context, Configuration>()); // force callback by accessing database var db = new Context(connectionStringName); var site = db.Sites.FirstOrDefault() // something to access the database return db; }
Я пришел к подобным выводам.
У нас была продолжительная дискуссия о том, что вчера. Взгляните на него.
Если соединение вызывается через DbContext ctor-это то, где появляются проблемы (упрощенно). As
DbMigrator
фактически вызывает ваш конструктор' default empty ' - так что вы получаете смесь вещей. У меня были некоторые действительно странные эффекты от этого. Я пришел к выводу, что обычный инициализаторCreateDb...
работает, но миграции не работают (и даже не работают, в некоторых случаях выбрасывают ошибки).Суть в том, чтобы каким-то образом сделать "синглетное" соединение-либо через Фабрику DbContext, как использовала @kirsten-или создание и изменение статического соединения внутри вашего DbContext - или аналогичного. Нет конечно, если это решит все проблемы, но должно помочь.
Для миграции вы можете либо (1) использовать
MigrateDatabaseToLatestVersion
, который автоматически включится, когда вы начнете использовать любую из сущностей в вашем контексте, либо (2) использоватьDbMigrator
, чтобы явно сказать EF, чтобы начать миграцию. Преимущество (2) заключается в том, что вам не нужно выполнять фиктивную операцию (например,AddJunk
в Примере @philsoady), и вы даже можете использоватьMigratorScriptingDecorator
, Если хотите извлечь SQL миграции (см. Пример 2 в коде)Трюк с (2), по-видимому, заключается в том, чтобы гарантировать, что то же самое строка соединения последовательно используется классами
DbMigrationsConfiguration
иDbContext
. Обратите внимание, что в ходеDbMigration.Update
создаются экземпляры нескольких контекстов - все они вызывают конструктор контекста по умолчанию (поэтому следите, если у вас есть несколько конструкторов). У вас также есть 2 варианта здесь - вы можете использоватьconnection string name
в приложении.config (но тогда вы не можете программно определить строку подключения) или build\hardcode\load и т. д... полныйconnection string
. Смотрите комментарии в коде ниже.Испытано в EF 6.0.1 & 6.0.2
using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Entity.Migrations; using System.Data.Entity.Migrations.Infrastructure; namespace ConsoleApplication1 { // Models public class Foo { [Key] public int Id { get; set; } public string Column1 { get; set; } public string Column2 { get; set; } } // Configuration public class Configuration : DbMigrationsConfiguration<Context> { public static string StaticConnectionString; // use connection string public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; TargetDatabase = new DbConnectionInfo(StaticConnectionString, "System.Data.SqlClient"); // use connection string //TargetDatabase = new DbConnectionInfo("ConnectionStringName"); // use connection string name in app.config } protected override void Seed(Context context) { } } // Context public class Context : DbContext { public Context() //: base("ConnectionStringName") // use connection string name in app.config : base(ConsoleApplication1.Configuration.StaticConnectionString) // use connection string { } public IDbSet<Foo> Foos { get; set; } } // App class Program { static void Main(string[] args) { // Example 1 - migrate to test1 DB Configuration.StaticConnectionString = "Data Source=localhost;Initial Catalog=test1;Integrated Security=True;MultipleActiveResultSets=True"; var configuration = new Configuration(); var migrator = new DbMigrator(configuration); migrator.Update(); Console.WriteLine("Migration 1 complete"); // Example 2 - create migrate SQL and migrate to test2 DB // NOTE: You can't do this if you use a connection string name in app.config // Generate migrate sql script for migration to test2 DB Configuration.StaticConnectionString = "Data Source=localhost;Initial Catalog=test2;Integrated Security=True;MultipleActiveResultSets=True"; configuration = new Configuration(); migrator = new DbMigrator(configuration); var scriptor = new MigratorScriptingDecorator(migrator); string sql = scriptor.ScriptUpdate(null, null); Console.WriteLine("Migration 2 SQL:\n" + sql); // Perform migration to test2 DB configuration = new Configuration(); migrator = new DbMigrator(configuration); migrator.Update(); Console.WriteLine("Migration 2 complete"); } } }
Посмотрите на эту ссылку: это дает вам больше свободы, чтобы активировать миграции самостоятельно для каждой базы данных.
Я решил эту проблему, используя статическую строку подключения к конкретной базе данных внутри конструктора по умолчанию.
Предположим, у меня есть несколько баз данных, все они основаны на одной схеме: myCatalog1, myCatalog2 и т. д. Я использую только первую строку подключения к базе данных в конструкторе следующим образом:public MyContext() : base("Data Source=.\SQLEXPRESS;Initial Catalog=myCatalog1;Integrated Security=True") { // Can leave the rest of the constructor function itself empty }
Этот конструктор используется только для работы команды
Add-Migration
и создавать миграции. Обратите внимание, что для остальных баз данных нет побочных эффектов, и если вам нужен другой конструктор для инициализации контекста (для других целей, кроме миграции), он будет работать.После того, как я выполню
Add-Migration
Вот так:Add-Migration -ConfigurationTypeName YourAppName.YourNamespace.Configuration "MigrationName"
Я могу вызвать следующий код (, взятый из ссылки, приведенной в начале ), чтобы обновить миграции к каждой из моих баз данных , которые основаны на той же схеме, что и myCatalog1:
YourMigrationsConfiguration cfg = new YourMigrationsConfiguration(); cfg.TargetDatabase = new DbConnectionInfo( theConnectionString, "provider" ); DbMigrator dbMigrator = new DbMigrator( cfg ); if ( dbMigrator.GetPendingMigrations().Count() > 0 ) { // there are pending migrations // do whatever you want, for example dbMigrator.Update(); }
Я хотел автоматически мигрировать при запуске в DEBUG, чтобы упростить его для разработчиков (производственный установщик обычно выполняет миграции), но у меня была та же проблема, указанная в коде строка подключения игнорируется при миграции.
Мой подход состоял в том, чтобы вывести мигрирующие контексты из этого универсального, который обрабатывает "сохранение" строки соединения:
public class MigrateInitializeContext<TDbContext, TMigrationsConfiguration> : DbContext where TDbContext : DbContext where TMigrationsConfiguration : DbMigrationsConfiguration<TDbContext>, new() { // ReSharper disable once StaticFieldInGenericType private static string nameOrConnectionString = typeof(TDbContext).Name; static MigrateInitializeContext() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<TDbContext, TMigrationsConfiguration>()); } protected MigrateInitializeContext(string nameOrConnectionString) : base(nameOrConnectionString) { MigrateInitializeContext<TDbContext,TMigrationsConfiguration>.nameOrConnectionString = nameOrConnectionString; } protected MigrateInitializeContext() : base(nameOrConnectionString) { } }
Предупреждение ReSharper связано с тем, что статические поля в универсальном классе являются только статическими для конкретного типа , который в наш случай-этоименно то, чего мы хотим.
Контексты определяются следующим образом:
public class MyContext : MigrateInitializeContext<MyContext, Migrations.Configuration> { public MyContext() { } public MyContext(string nameOrConnectionString) : base(nameOrConnectionString) { } public virtual DbSet<MyType> MyTypes { get; set; } }
Который можно использовать нормально.