ASP.net Entity Framework Code First custom initializer never called - c#

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)

Related

EF6 change database create strategy

I have an application where I want to use the FILESTREAM feature for storing blobs. I know that EF6 does not support FILESTREAM, so I will manage file and image handling by myself. However, I have to write my own initialize code to add a FILEGROUP and FILE to my Database and:
public class CustomDbInitializer : CreateDatabaseIfNotExists<DatenbankContext>
{
public override void InitializeDatabase(DatenbankContext context)
{
base.InitializeDatabase(context);
SqlConnectionStringBuilder b = new SqlConnectionStringBuilder(context.Database.Connection.ConnectionString);
context.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction,"ALTER DATABASE "+ b.InitialCatalog + " ADD FILEGROUP GRP CONTAINS FILESTREAM");
//more sql commands
}
}
Then, I call manually the function InitializeDatabase(context)
new CustomDbInitializer().InitializeDatabase(context);
However, I find this a bit clunky and counter-intuitive. I know that I can set an initializer for the context in the constructor, but this does not work the way I want:
public class DatenbankContext : DbContext
{
public DatenbankContext()
:base("name=name")
{
Database.SetInitializer(new CustomDbInitializer());
}
}
Basically, I want the call
new DatenbankContext() // or
context.Database.CreateIfNotExists()
to initialize my Database with my custom strategy in an intuitive way. How can this be done?
Inside your custom initializer add:
if (!context.Database.Exists())
{
context.Database.Create();
}
// Do next steps
Call your initializer inside a static constructor. This insures it only runs once:
public class DatenbankContext : DbContext
{
static DatenbankContext()
{
Database.SetInitializer(new CustomDbInitializer());
}
}
See here for more on custom initializers. You probably don't want to inherit from CreateDatabaseIfNotExists if you want control over the process. See the example in link.

Entity Framework, Automatic apply Migrations

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.

Entity Framework 6 with dynamic data handle database exception override object context savechanges

I would like to handle asp.net dynamic data database exceptions in EF6.
So I decided to achive this like example bellow (to show error on dynamic data page).
public partial class MyCustomDataContext : DbContext{
public override int SaveChanges()
{
try
{
base.SaveChanges();
}
catch(Exception)
{
throw ValidationException(null, "My message");
}
}
The problem is that I when I click save in dynamic data I don't go to my overriden function. I found in net that dynamic data to save changes use DbContext's underlying ObjectContext.
So my question is how can I override that ObjectContext SaveChanges method to show user custom error message when I get UpdateExcetpion ?
OK I have tested this and spoken to the Asp.Net team and the issue us that the EF DataSource actually still uses the ObjectContext by unwrapping the DbContext to get at the ObjectContext and therefor the SaveChanges override never get called what you need to do is:
public partial class NorthwindEntities : DbContext
{
public NorthwindEntities()
: base("name=DataModel")
{
((IObjectContextAdapter)this).ObjectContext.SavingChanges += ObjectContext_SavingChanges;
}
private void ObjectContext_SavingChanges(object sender, EventArgs e)
{
throw new NotImplementedException();
}
So in this SavingChanges event you will need to do things in the Objwect Context way.
For wrapping your errors you will need to use EF6.x and use the
Logging and Intercepting Database Operations
Thanks to Rowan and Pranav from the EF and Asp.net Teams.

Two different instances of my dbcontext in EF are colliding with "The context cannot be used while the model is being created."

I have a WebAPI solution with an endpoint "DeleteFolder". That looks like this:
public FolderController()
{
_service = new DtoService();
}
[HttpDelete]
public HttpResponseMessage DeleteFolder(int id)
{
_service.DeleteFolder(id);
return Request.CreateResponse(HttpStatusCode.OK, "Deleted");
}
My _service is an instance of DtoService.cs which contains this:
public DtoService()
{
_repository = new RepositoryService();
}
public void DeleteFolder(int folderId)
{
_repository.DeleteFolder(folderId);
}
Finally, in my repository I have this:
public RepositoryService()
{
_db = new AppDbContext();
}
public void DeleteFolder(int folderId)
{
var folder = GetFolder(folderId);
_db.Folders.Remove(folder);
SaveChanges();
}
Where _db is an instance of my project's DbContext, defined once in the constructor of the Repository class.
When I send a bunch of asynchronous AJAX calls to the delete method, I get "The context cannot be used while the model is being created.". I can see a new instance of RepositoryService spinning up for each one, but if I set a breakpoint at
var folder = GetFolder(folderId);
and then step over, it's hit again, so it seems the other instance is trying to hit the same code before the first one completes, which is somehow causing this error to be thrown.
I don't have any references to my dbContext in my WebAPI layer or DTO service layer - and I'd prefer to keep it that way if possible. I tried wrapping the DeleteFolder code in a using (_db = new AppDbContext) but this didn't work either - and I feel like peppering all of my repository methods with a new dbcontext can't possibly be a recommended solution.
What's going on here? Any tips would be awesome, I'm totally at a loss here.
One thread is initializing your context in response to a request (it's a lengthy process), and another comes in attempting to use the context. The second request thinks the context is ready for use and you get this exception: “The context cannot be used while the model is being created.”
The following code shows how to force database initialization in EF Code First at start up:
protected void Application_Start() {
// ... 
     // Initializes and seeds the database.
     Database.SetInitializer(new MyDBInitializer());
 
     // Forces initialization of database on model changes.
     using (var context = new ApplicationDB()) {
          context.Database.Initialize(force: true);
     }
  // ...
}

How to create initializer to create and migrate mysql database?

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.

Categories