Run different database types in different environments in dotnet core 2 - entity-framework

I want to run a SQLite database in development and a SQLServer Express database in production.
We are using code first with database migrations.
How do I inject a different dbcontext in each environment?
How do I run migrations against a specific database. E.g. In development I'll want to run migrations against the SQLite database.

So I guess I found a nice way for you to do that. You can use the ConfigureDevelopmentServices startup convention to add your SQLSite DbContext. So, just as some basic example you would have:
// Production "like" ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
// Use Sql Server
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ProductionConnection")));
}
// Development ConfigureServices
public void ConfigureDevelopmentServices(IServiceCollection services)
{
// Use SQL Lite
services.AddDbContext<SchoolContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("DevelopmentConnection")));
}
You can even go further and add a ConfigureStagingServices if you happen to have another different context for staging only. To avoid copy and pasting of common services, you could have a private method that register the common services and have the separate ones only with specific stuff.
Now for the migrations, I never tested this but, my best guess is if you have the correct dbContext and the correct connection string, the migrations will work fine. You just point to the EF project and run it.

For the official MS response, see Use SQLite for development, SQL Server for production

Related

Failed initial migration in Entity Framework Core on .NET 6 Isolated [duplicate]

.NET6 EFCore & Cosmos Migration issue. Need some help.
Hello folks. I am new in the world of .Net and I am facing an issue that Google has failed to help me solve. You're kind of my last regard.
So. I am trying to connect to an Azure Cosmos DB from my little HomeControlCenter Project using EFCore 6.0.3
The Error:
Unable to resolve service for type 'Microsoft.EntityFrameworkCore.Migrations.IMigrator'. This is often because no database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext>
object in its constructor and passes it to the base constructor for DbContext.
My Program.cs:
builder.Services.AddDbContext<ControlCenterContext>(options =>
options.UseCosmos(builder.Configuration.GetConnectionString("DefaultConnection"), "ToDoList"));
My DbContext Impl:
public class ControlCenterContext : DbContext
{
public ControlCenterContext(DbContextOptions<ControlCenterContext> options) : base(options)
{
}
}
I also tried to use an override of OnConfiguring instead of the Program.cs line.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseCosmos(
"<AccountEndpoint>",
"<accountKey>",
databaseName: "ToDoList");
Nothing helped. When ever I run dotnet ef migrations add "initialSetup" I get the error mentioned above.
I read the error carefully and as you can see, I did apply all the necessary constructor params & other additions... I even tried to create a vanilla project and do the same all over again...
I couldn't find anything official from Microsoft, but the author of this blog states migrations using EF Core for CosmosDb are not supported: https://www.thereformedprogrammer.net/an-in-depth-study-of-cosmos-db-and-ef-core-3-0-database-provider/#1-no-migrations-can-cause-problems
This makes sense since CosmosDB is a document database, so it has no schema, it's just a bunch of JSON files. I ran into this issue when I wanted to use migrations to make seed data. The only solution I could think of was to create a separate project that uploaded the seed data with static values. But again, this was only seed data and not schema updates.

Seamless EF Migrations from Staging > Production with Schema Change

I have a simple web app. It consists of an Azure Web App with Staging and Production slots. When there are no DB migrations to consider, I can easily achieve a seamless update by:
Deploy App to Staging
Swap Staging <> Production Slots
This gets trickier when I have a DB migration to handle. Right now what I do is:
Deploy App to Staging
When deployment is ready, run update-database to Prod (no staging database)
Swap Staging <> Production Slots
This means that I still effectively have downtime as 2 + 3 don't happen simultaneously, which means that for some seconds, my users will experience imperfect behavior as the 'DB schema has changed'.
What is the simplest solution here? I'm thinking I may have to spin up a staging database too, but then I have to worry about replication and connection string management which adds a bit of overhead.
We had the same dilemma when moving our solution to continuous delivery model and wanted to avoid downtime.
You need to configure your EF to run Code-First on development environment and Database-First in production.
This makes it possible to push your changes to live in three stages:
Stage 1. Database Migrations
At this stage, you will use EF's migrate.exe utility (or simply script them before hand) to run your latest migrations against the live database. After migrations are applied your website in production still keeps functioning as nothing has happened (because it's configured to be database-first).
The important bit is that you need to make sure your migrations at this stage are additive, in the sense that it would'd change a let's say table or column that will cause the live site to crash. It may look scary, but if your project is mature enough, you soon will realise that most of changes to your schema are either completely additive or can be broken down into two stages. (see stage 3)
Stage 2. Update production website
At this stage do your normal Staging --> Production website deployment.
Stage 3. Database Migrations (part 2)
In those rare cases where you had for example a database table or column renamed, you will need to consider breaking it into two steps of:
Add a new column (done in part 1)
Remove old column and migrate data (done in part 2).
Appendices
EF Database-First only in production
In your Startup.cs or Global.asax.cs:
#if DEBUG
Database.SetInitializer(new MigrateDatabaseToLatestVersion<AppDatabase, Migrations.Migrations.Configuration>());
#else
Database.SetInitializer(new RequireDatabaseToBeUpToDate<AppDatabase, Migrations.Migrations.Configuration>());
#endif
This does exactly what it says on the tin:
On Local: Migrates it's database to latest migration.
In Production: Ensures that the database migrations is NOT AHEAD of the model assembly it is using. -- this is a safety measure making sure even if we ever accidentally deployed web before database, it stops the site from firing up.
public class RequireDatabaseToBeUpToDate<TContext, TMigrationsConfiguration> : IDatabaseInitializer<TContext>
where TContext : DbContext
where TMigrationsConfiguration : DbMigrationsConfiguration, new()
{
public void InitializeDatabase(TContext context)
{
var migrator = new DbMigrator(new TMigrationsConfiguration());
var migrations = migrator.GetPendingMigrations().ToList();
if (migrations.Any())
{
var message = "There are pending migrations that must be applied (via a script or using migrate.exe) before the application is started.\r\n" +
$"Pending migrations:\r\n{string.Join("\r\n", migrations)}";
throw new MigrationsPendingException(message);
}
}
}
Running migrations against live database
$migrate = "<path>\migrate.exe"
$migrateConfig = "<path>\migrate.exe.config"
$connectionString = <your-live-connection-string>
& $migrate <your-project-migration-assembly> /startupConfigurationFile=$migrateConfig <your-migration-configuration-type-name> /connectionString=$connectionString /connectionProviderName=System.Data.SqlClient /verbose

Stop a referenced dll from running migrations

I have a solution with a web application. This application uses Entity Framework and Code First. Then I have a second project, a console application. This console application shares the assembly that hold the migrations.
Is there a way to this console application that it should never run the Migrations? It could happen that this console application would contain a newer version which has not yet been deployed in the web application. I like to make sure there is no risk for the console to ruin the web application. Better to just have the console failing that updating the database.
Set the database initialiser to null in your DbContext constructor:
public class ConsoleContext : DbContext
{ public ConsoleContext()
: base("Name=" + Config.ConnectionStringName)
{
// Prevent attempt to initialize a database for this context
Database.SetInitializer<DtoContext>(null);
}
}
Alternatively,
in your web application:
protected void Application_Start()
{
Database.SetInitializer<MyDbContext>(
new MigrateDatabaseToLatestVersion<MyDbContext, Migrations.Configuration>());
}
and in your console application:
protected void Application_Start()
{
Database.SetInitializer<DtoContext>(null);
}
And, to be doubly certain, use two different database users. The web application will need db_datareader, db_datawriter and db_ddladmin roles if you're using Sql Server. The console application should only be in db_datareader and db_datawriter roles to prevent it changing the database schema
Easy) Yes, I have a good solution for that. in Nuget Console you have to provide the connection string and the target migration for the commands: Add-Migration/Update-Database.
Need Code) You can also do that programmatically by using DbMigrator.

EF Code first migrations not running after deploy to Azure

I have two folders for my migrations (AuthContext and UserProfileContext), each has their own migration and some custom sql to run afterwards for data migrations and whatnot.
This works fine when using package manager console. I
Restore from production
Run Update-Database -ConfigurationTypeName Migrations.Auth.Configuration
Run Update-Database -ConfigurationTypeName Migrations.UserProfile.Configuration
Then everything is very happy in the new database, migrations executed data shuffled where it needs to.
I tried to test out the migrations on publish piece by:
Restore production on dev database
Single connection string (all contexts use the same) pointed to dev database
Publish to azure web site
Checked the box for Apply Code First Migrations
Selected that single connection string
Okay it published fine; however, when I went to look at the database, nothing happened! It did not create the necessary tables, columns, or data moves.
TLDR; Code first migrations are not running after publish to Azure
Update 1
I've tried any combination of the below: only one single connection string so I'm guessing that's not the issue, and execute migrations is checked.
On publish the api runs but no database changes are made. I thought perhaps I needed to hit it first but I just get random errors when I try to use the api (which now of course relies on the new database setup), and the database is still not changed.
I've seen a couple references out there about needing to add something to my Startup class but I'm not sure how to proceed.
Update 2
I solved one issue by added "Persist Security Info=True" to my connection string. Now it actually connects to the database and calls my API; however, no migrations are running.
I attached debugger to Azure dev environment and stepped through... on my first database call it steps into the Configuration class for the Migration in question, then barfs and I can't track down the error.
public Configuration()
{
AutomaticMigrationsEnabled = false;
MigrationsDirectory = #"Migrations\Auth";
ContextKey = "AuthContext";
}
Update 3
Okay, dug down and the first time it hits the database we're erroring. Yes this makes sense since the model has changed, but I have migrations in place, enabled, and checked! Again, it works fine when running "Update-Database" from package manager console, but not when using Execute Code First Migrations during publish to Azure
The model backing the 'AuthContext' context has changed since the
database was created. Consider using Code First Migrations to update
the database (http://go.microsoft.com/fwlink/?LinkId=238269).
Update 4
Okay I found the root issue here. VS is setting up the additional web.config attrib for databaseInitializer on only one of my database contexts, the one not mentioned is in fact hit first from my app.
So now I have to figure out how to get it to include multiple contexts, or, combine all of my stuff into a single context.
The answer to this post is not very detailed.
This article explains what I had to do to fix a similar problem to this:
https://blogs.msdn.microsoft.com/webdev/2014/04/08/ef-code-first-migrations-deployment-to-an-azure-cloud-service/
I'll roughly describe the steps I had to take below:
Step 1
Add your connection strings to your dbContexts, in my situation, they were both the same.
Step 2
Add this to your web.config
<appSettings>
<add key="MigrateDatabaseToLatestVersion" value="true"/>
</appSettings>
Step 3
And add this to the bottom of your global.asax.cs / Startup.cs(OWIN startup)
var configuration = new Migrations.Configuration();
var migrator = new DbMigrator(configuration);
migrator.Update();
Solved! To summarize the solution for posterity:
Enable Code First Migrations only enables them for one base connection string per checkbox checked, regardless of how many contexts have migrations against that base connection string. So in my case I broke out the two in question into two different connection strings.
Then I was hitting other errors and identified that if you're changing the base connection string to the model backing asp identity you need to include (one time publish) the additional flag base("AuthContext" , throwIfV1Schema: false)
For anyone who has this issue and may have overlooked the following: be sure to check that you have correctly set the connection string in your Web.config file and/or Application settings on Azure. This includes DefaultConnection and DefaultConnection_DatabasePublish.
In our case the former was correct but the latter contained the wrong database instance because it had been carried over from an App Service clone operation. Therefore the wrong database was being migrated.

How to initialize EF Code First Migrations on a remote target

I would like to introduce Code First Migrations to my project, but I am unsure of how to handle deploying this to my client for testing. Until now, things have been quite simple, and I have just used a CreateDatabaseIfNotExists initializer. Now, I have two scenarios:
He deletes his existing, before-migrations, database, and uses an initializer to create a new, with-migrations, database, and we use migrations from here on to upgrade his database. Can I use the MigrateDatabaseToLatestVersion initializer to create the DB if missing as well?
I just deploy my code and let it perform migrations. I'm not quite sure if anything but using a MigrateDatabaseToLatestVersion is required here. Will this upgrade a pre-migrations database to one suitable for migrations?
This is what I do when automatic migration is required; I hope this helps you find a solution:
Database.SetInitializer(
new MigrateDatabaseToLatestVersion<ContextFileName, PathToMigrationsConfig>()
);
Database.Initialize(false);
In the configuration file for the migrations, I set the following in the constructor
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = false;
In the configuration file you should have an override of the seed method, if not you can add it and fill in your seed data.
What the above will do is create/upgrade the database to the latest as long as no data loss occurs. This should allow you hand off the code to the client.
On a side note, for a production system I will usually argue the point of not doing this. This had many disadvantages. For databases I do not have control over I have yet to find a client that has refused the generated script file.
You can get this by using the following command after you add a migration through the Package Manager Console:
Update-database –script -verbose