I am using Entity Framework Code First approach with AutomaticMigrationsEnabled = true:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DbContext, MigrateDBConfiguration>());
//////////////////////////////////
public class MigrateDBConfiguration : System.Data.Entity.Migrations.DbMigrationsConfiguration<DbContext>
{
public MigrateDBConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
}
The first run of the project creates the database and tables as expected. After changing my model by adding or dropping fields, I ran Add-Migration. The Migration class was generated but after running the project this exception occurs:
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll but was not handled in user code
Additional information: The model backing the 'DBContext' context has
changed since the database was created.
EDIT: Per the guidance in the answer of arturo menchaca I changed my code like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DBContext, MigrateDBConfiguration<DBContext>>());
...
After the change this exception is occurring:
There is already an object named 'MyTable' in the database.
How can I apply my database migration?
Automatic Migrations means that you don't need to run add-migration command for your changes in the models, but you have to run update-database command manually.
If Automatic Migrations is enabled when you call update-database, if there are pending changes in your models, an 'automatic' migration will be added and database will be updated.
If you want that your database is updated without need to call update-database command, you can add Database.SetInitializer(...) in OnModelCreating() method on your context, like so:
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MigrateDBConfiguration>());
}
...
}
public class MigrateDBConfiguration : System.Data.Entity.Migrations.DbMigrationsConfiguration<MyContext>
{
...
Note that you should declare DbMigrationsConfiguration and MigrateDatabaseToLatestVersion with your real context, not the default DbContext.
Finally, I found a solution to my problem. I call this method in each application start :
public void InitializeDatabase(DataAccessManager context)
{
if (!context.Database.Exists() || !context.Database.CompatibleWithModel(false))
{
var configuration = new DbMigrationsConfiguration();
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);
var script = scriptor.ScriptUpdate(null, migrations.Last());
if (!string.IsNullOrEmpty(script))
{
context.Database.ExecuteSqlCommand(script);
}
}
}
}
If you have change in your entities, you need first run add-migration to create the migration script.
After that in your Global.asax
you need to have some code like this
var configuration = new MyProject.Configuration();
var migrator = new System.Data.Entity.Migrations.DbMigrator(configuration);
migrator.Update();
every time that you run your asp.net project it'll check if you have a new migration to run and run update-database automatically for you.
Microsoft addresses migrations at runtime, here.
For example, you can do this in Program.cs: (tested working in .NET 5.0 preview)
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
MigrateDatabase(host);
host.Run();
}
private static void MigrateDatabase(IHost host)
{
using var scope = host.Services.CreateScope();
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
using Microsoft.EntityFrameworkCore;
await _dbContext.Database.MigrateAsync();
_dbContext.Database.Migrate();
OR
await _dbContext.Database.EnsureCreatedAsync();
_dbContext.Database.EnsureCreated();
both method check if database exist, if not they both create it.
Migrate() uses migrations and is suitable if you use migrations or relational database.
EnsureCreated() does not use migrations which means once db is created using this method no further migrations can be executed over it.
Related
I am new to EF Core 3.1.22 using it with my .NET 4.6 desktop application. I am able to create the database and migrations using the package manager console, but my code doesn't generate the database file on its own at first run, when the database doesn't exist yet.
If I create the database using Update-Database from the package manager console, the file is created.
How can I update this code to generate the database file from code at first run and then apply database.migrate on that?
public class MyDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>, IMyDbContextFactory
{
public MyDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseSqlite($#"Data Source={DbConfiguration.GetDatabasePath()};", options =>
{
options.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName);
});
return new MyDbContext(optionsBuilder.Options);
}
public MyDbContext CreateDbContext()
{
var dbcontext= CreateDbContext(new[] { "" });
dbcontext.Database.Migrate();
return dbcontext;
}
}
Your connection string doesn't allow the database to be created, see the SQLite provider docs.
Either change it to Mode=ReadWriteCreate or remove it entirely as it's the default setting.
I'm using EF Core version 2.1.11 with SQL Server Express version 12.0.2000.
I have my entity:
public class MyEntity
{
public string Id { get; set; }
public int Age { get; set; }
}
and my Db context:
public class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<MyEntity>();
}
}
In order to create migrations using dotnet ef migrations add InitialCreate, I also have a design-time factory defined like:
public class MyContextFactory : IDesignTimeDbContextFactory<MyContext>
{
public MyContext CreateDbContext(string[] args)
{
var options = new DbContextOptionsBuilder<MyContext>()
.UseSqlServer("server=localhost\\SQLEXPRESS;Initial Catalog=MyTestDB;Integrated Security=true")
.Options;
return new MyContext(options);
}
}
Note that, in my SQL Express, I have not created a database named MyTestDB. I want EF Core to create this database and apply the migrations. So, my main method that attempts this is:
class Program
{
static void Main(string[] args)
{
using (var ctx = new MyContextFactory().CreateDbContext(Array.Empty<string>()))
{
ctx.Database.Migrate();
}
}
}
Now, the line ctx.Database.Migrate() raises the exception:
System.Data.SqlClient.SqlException: 'Cannot open database "MyTestDB" requested by the login. The login failed.
Login failed for user
This exception is being raised because the MyTestDB is not in SQL Express server at the time of opening the connection. What is strange is, when I run the main method, it raises this exception like 15 times (which is suppressed unless the exception settings are enabled) but when the program exits, and I check the database, the database is actually created and the migrations are applied.
My question is how can I avoid this exception being raised (I do not want to create the database before running the code)? What is the proper way to use the connection string and the Database.Migrate() method?
I have some code in my ConfigureServices that fails when running a migration:
dotnet ef migrations list
I'm trying to add a Certificate but it can't find the file (it works when starting the project as a whole). So is there a way to do something like this:
if (!CurrentEnvironment.IsMigration()) {
doMyStuffThatFailsInMigration()
}
That way I could keep my code as it is but just execute it when not running it in a migration.
Thanks
Just set a static flag in the Main method (which is not called by the dotnet-ef tool):
public class Program
{
public static bool IsStartedWithMain { get; private set; }
public static void Main(string[] args)
{
IsStartedWithMain = true;
...
}
}
and then check it when needed:
internal static void ConfigureServices(WebHostBuilderContext context, IServiceCollection services)
{
if (Program.IsStartedWithMain)
{
// do stuff which must not be run upon using the dotnet-ef tool
}
}
EDIT: in Dotnet 6.0 there's no separate ConfigureServices method. Everything is initialized in the Main method (can be created with dotnet new .. --use-program-main). In this case a flag can be used for skipping EF stuff:
private static bool IsStartedWithMain =>
Assembly.GetEntryAssembly() == Assembly.GetExecutingAssembly();
My current solution to detecting if a migration has not occurred:
using System.Linq;
// app is of type IApplicationBuilder
// RegisteredDBContext is the DBContext I have dependency injected
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetService<RegisteredDBContext>();
if (context.Database.GetPendingMigrations().Any())
{
var msg = "There are pending migrations application will not start. Make sure migrations are ran.";
throw new InvalidProgramException(msg);
// Instead of error throwing, other code could happen
}
}
This assumes that the migrations have been synced to the database already. If only EnsureDatabase has been called, then this approach does not work, because the migrations are all still pending.
There are other method options on the context.Database. GetMigrations and GetAppliedMigrations.
I have been learning how to use EF for a week or so now and am stuck on the issue of creating/updating my database. I am able to create an initializer to create the database if it is not there:
static class Program
{
static void Main()
{
Database.SetInitializer<GumpDatabase>(new GumpDatabaseInitializer());
....
class GumpDatabaseInitializer : CreateDatabaseIfNotExists<GumpDatabase>
{
public GumpDatabaseInitializer()
{
}
protected override void Seed(GumpDatabase context)
{
context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX Name ON Stations (Name)");
// Other stuff
}
}
Or I can create a Configuration to migrate the db
static class Program
{
static void Main()
{
Database.SetInitializer<GumpDatabase>(new MigrateDatabaseToLatestVersion<GumpDatabase, Configuration>());
....
internal sealed class Configuration : DbMigrationsConfiguration<GumpDatabase>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
SetSqlGenerator("MySql.Data.MySqlClient", new MySql.Data.Entity.MySqlMigrationSqlGenerator());
}
protected override void Seed(GumpDatabase context)
{
}
Each works correctly but I haven't figured out a way to do both. I can switch between the two initializers by changing the SetInitializer call but if I want to create the database if it is not there and also migrate it if it is what do I do? Do I need to create a custom initializer?
Thanks
Edit based on NSGaga answer
class CreateOrMigrateDatabaseInitializer<TContext, TConfiguration> : CreateDatabaseIfNotExists<TContext>, IDatabaseInitializer<TContext>
where TContext : DbContext
where TConfiguration : DbMigrationsConfiguration<TContext>, new()
{
private readonly DbMigrationsConfiguration _configuration;
public CreateOrMigrateDatabaseInitializer()
{
_configuration = new TConfiguration();
}
public CreateOrMigrateDatabaseInitializer(string connection)
{
Contract.Requires(!string.IsNullOrEmpty(connection), "connection");
_configuration = new TConfiguration
{
TargetDatabase = new DbConnectionInfo(connection)
};
}
void IDatabaseInitializer<TContext>.InitializeDatabase(TContext context)
{
Contract.Requires(context != null, "context");
if (context.Database.Exists())
{
if (!context.Database.CompatibleWithModel(throwIfNoMetadata: false))
{
var migrator = new DbMigrator(_configuration);
migrator.Update();
}
}
else
{
context.Database.Create();
Seed(context);
context.SaveChanges();
}
}
protected virtual void Seed(TContext context)
{
}
}
and
internal sealed class Configuration : DbMigrationsConfiguration<GumpDatabase>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = false;
SetSqlGenerator("MySql.Data.MySqlClient", new MySql.Data.Entity.MySqlMigrationSqlGenerator());
}
protected override void Seed(GumpDatabase context)
{
}
}
and
class GumpDatabaseInitializer : CreateOrMigrateDatabaseInitializer<GumpDatabase,Gump.Migrations.Configuration>
{
public GumpDatabaseInitializer()
{
}
protected override void Seed(GumpDatabase context)
{
context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX Name ON Stations (Name)");
context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX Name ON Sequences (Name)");
context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX StationPartNumber ON StationPartNumbers (StationId,PartNumberId)");
}
}
and finally
static void Main()
{
Database.SetInitializer<GumpDatabase>(new GumpDatabaseInitializer());
I think you're pretty much there - you can lookup the source code for MigrateDatabaseToLatestVersion (it's open source http://entityframework.codeplex.com/) - it's pretty simplistic, what it does pretty much is call the DbMigrator - as far as I could see.
All you have to do seems is to merge the two - use one or the other as a basis, add other functionality in there - that should work fine I think.
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)
{
Contract.Requires(context != null, "context");
var migrator = new DbMigrator(_configuration);
migrator.Update();
// move on with the 'CreateDatabaseIfNotExists' for the 'Seed'
base.InitializeDatabase(context);
}
protected override void Seed(TContext context)
{
}
}
call it like this...
Database.SetInitializer(new CreateAndMigrateDatabaseInitializer<GumpDatabase, YourNamespace.Migrations.Configuration>());
...actually, override it (since it's generic implementation) like you were doing for CreateDatabaseIfNotExists (you just have extra 'param' for Configuration) - and just supply the 'Seed'.
class GumpDatabaseInitializer : CreateAndMigrateDatabaseInitializer<GumpDatabase, YourNamespace.Migrations.Configuration>
{
protected override void Seed(GumpDatabase context)
{
context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX Name ON Stations (Name)");
}
}
...and call it something like
Database.SetInitializer(new GumpDatabaseInitializer());
EDIT:
Based on the comments - DbMigrator should not run twice. It always checks (spends a bit of time) and does a 'blank' update and moves on. However just in case if you'd like to remove that and 'check' before entering - this should work (change the similar piece above)...
var migrator = new DbMigrator(_configuration);
if (!context.Database.CompatibleWithModel(throwIfNoMetadata: false))
if (migrator.GetPendingMigrations().Any())
migrator.Update();
(this is a redundant / double-check - one of the if-s should be enough. Put a break there - and see exactly what's happening, it should not get in - once Db is migrated. As I mentioned, works fine when I test it.
EDIT:
Replace the inside of InitializeDatabase with...
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(throwIfNoMetadata: false))
if (migrator.GetPendingMigrations().Any())
migrator.Update();
// move on with the 'CreateDatabaseIfNotExists' for the 'Seed'
base.InitializeDatabase(context);
if (doseed)
{
Seed(context);
context.SaveChanges();
}
This works around (half-way) not-seeding - if migration goes first. And migrations have to be first, otherwise you have issues.
You still need to do it properly - this is the gist if not all you might need - but if any issues w/ MySQL etc., probably some more leg work here.
Note: Still seeding doesn't call if you have a db, but it's empty. Problem is mixing of the two different initializers. So you'll have to work that out - either by implementing what Create... does inside (that call we can't call) or something else.
Actually it should be:
var migrator = new DbMigrator(_configuration);
if (!context.Database.CompatibleWithModel(false) || migrator.GetPendingMigrations().Any())
migrator.Update();
because if we have a migration, that is not related to our db model, for example inserting a row in any of our tables, the migration won't be executed.
To do both (seed and migrate) you really only have to use migrations with a MigrateDatabaseToLatestVersion initializer. When you enable migrations for your context a Configuration class derived from DbMigrationsConfiguration is created and you can override the Seed method to seed your database. Note that the database may already contain seed data when this method executes but the AddOrUpdate extension method conveniently helps you make "upserts" in your database.
This is different compared to the Seed method of some of the other database intitializers where the database is only seeded when it is initially created. However, when you are using migrations you may want to change your seed data when the database changes and using MigrateDatabaseToLatestVersion makes that possible.
To combine seeding with migrations you will have to perform the following steps in a new project:
Create a code-first DbContext with associated entities
In the package manager console execute the command Enable-Migrations
In the Migrations folder a Configuration class is generated with a Seed method. You can modify this method to seed your database:
protected override void Seed(MyContext context) {
// Add two entities with name "Foo" and "Bar".
context.MyEntities.AddOrUpdate(
e => e.Name,
new MyEntity { Name = "Foo" },
new MyEntity { Name = "Bar" }
);
}
You need to create a database initializer that derives from MigrateDatabaseToLatestVersion:
class MyContextInitializer
: MigrateDatabaseToLatestVersion<MyContext, Migrations.Configuration> { }
You will also have to configure the initializer either by calling Database.SetInitializer(new MyContextInitializer()) when you application starts or in the App.config file by using the <databaseInitializer/> element.
In the constructor for the generated Configuration class you can enable automatic migrations:
public Configuration() {
AutomaticMigrationsEnabled = true
}
However, in a team you might prefer to not do that. In that case you will have to create an initial migration (unless it was created when you did Enable-Migrations). In the package manager execute the command Add-Migration InitialCreate. This creates the first migration required to create your database.
At this point you have a DbContext with migrations and a Seed method.
So to sum it: Enable migrations, use the MigrateDatabaseToLatestVersion initializer and add seed data in the Configuration class that was generated when migrations were enabled.
While MigrateDatabaseToLatestVersion does actually create the DB if it does not exist and even allows you to seed it, if you already have a working solution based on CreateDatabaseIfNotExists and/or don't want to complicate it with testing for existence of seed data, you can just use the below by inheriting from it rather than from CreateDatabaseIfNotExists:
public class CreateOrMigrateDatabaseInitializer<TContext, TConfiguration> : CreateDatabaseIfNotExists<TContext>, IDatabaseInitializer<TContext>
where TContext : DbContext
where TConfiguration : DbMigrationsConfiguration<TContext>, new()
{
void IDatabaseInitializer<TContext>.InitializeDatabase(TContext context)
{
if (context.Database.Exists())
{
if (!context.Database.CompatibleWithModel(throwIfNoMetadata: false))
{
var migrationInitializer = new MigrateDatabaseToLatestVersion<TContext, TConfiguration>(true);
migrationInitializer.InitializeDatabase(context);
}
}
base.InitializeDatabase(context);
}
}
This is based on previous answers and OP's own solution. This should work with other providers as well, but I only tested with SQL Server.
I am using EF Code First in an Asp.net project. Code First created my database just fine but I am having fits getting it to seed my database with data. I am using a custom database initializer like so:
namespace Toolkit.Model
{
public class EntitiesContextInitializer : DropCreateDatabaseIfModelChanges<ToolkitContext>
{
protected override void Seed(ToolkitContext context)
{
List<Server> Servers = new List<Server>
{
new Server { ServerName = "Server 16" },
new Server { ServerName = "Server 29" }
};
foreach (Server server in Servers)
{
context.Servers.Add(server);
}
context.SaveChanges();
}
}
}
I set the initializer in the the Global.asax.cs and force the database to initialize like so:
using Toolkit.Model;
namespace Toolkit
{
public class Global : System.Web.HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
Database.SetInitializer(new EntitiesContextInitializer());
var context = new ToolkitContext();
context.Database.Initialize(false);
}
...
}
The problem is that my Initializer never gets called and so my database never gets populated. I have even tried changing the initializer to inherit DropCreateDatabaseAlways instead but still nothing... What am I missing here?
EDIT
So after dropping the database my initializer got called fine. However I still wonder why it didn't get called in other instances such as when I added a table (so the model changed) or when I changed the initialzer to be DropCreateDatabaseAlways.
I'm using a similar configuration for a SQL CE database, but instead of DropCreate, I'm using CreateIfNotExists.
The difference between what I have and yours is that you have are creating a context and calling initialize on the database. Based on that, I would suggest that you remove the context setup and the Database.initialize calls that you have in the application_start method.
try
Database.SetInitializer<MyContextName>(new EntitiesContextInitializer)
in EF 5.0 the library is declared as
public static void SetInitializer<TContext>(IDatabaseInitializer<TContext> strategy) where TContext : DbContext;
You can use Database Migrations with Code-First. Whenever you use Update-database command when your model changes, your Seed method (in Migrations) will be called.
This article explains more:
http://msdn.microsoft.com/en-gb/data/jj591621
You can also force it in Application_Start() your way:
Database.SetInitializer(new EntitiesContextInitializer());
var context = new ToolkitContext();
context.Database.Initialize(true);
At the end of your overriden menthod, Seed, in class, EntitiesContextInitializer, place at the end of the method add a line:
base.Seed (context)