Entity Framework Core 2.1 - Multiple Providers - c#

What is the right way to work with multiple providers?
My Example:
appsettings.json
{
"ConnectionStrings": {
"Sqlite": "Data Source=database.db"
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("Sqlite")));
}
DatabaseContext.cs
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
public DbSet<TestModel> TestModel{ get; set; }
}
A easy way for multiple providers?

A solution with only one Context (Example for SQLite + MySQL + MSSQL + PostgreSQL (or others)):
appsettings.json
{
// Add Provider and ConnectionStrings for your EFC drivers
// Providers: SQLite, MySQL, MSSQL, PostgreSQL, or other provider...
"Provider": "SQLite",
"ConnectionStrings": {
"SQLite": "Data Source=mydatabase.db",
"MySQL": "server=localhost;port=3306;database=mydatabase;user=root;password=root",
"MSSQL": "Server=(localdb)\\mssqllocaldb;Database=mydatabase;Trusted_Connection=True;MultipleActiveResultSets=true",
"PostgreSQL": "Host=localhost;Database=mydatabase;Username=root;Password=root"
}
}
Single DatabaseContext.cs
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
// add Models...
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Check Provider and get ConnectionString
if (Configuration["Provider"] == "SQLite")
{
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("SQLite")));
}
else if (Configuration["Provider"] == "MySQL")
{
services.AddDbContext<DatabaseContext>(options =>
options.UseMySql(Configuration.GetConnectionString("MySQL")));
}
else if (Configuration["Provider"] == "MSSQL")
{
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MSSQL")));
}
else if (Configuration["Provider"] == "PostgreSQL")
{
services.AddDbContext<DatabaseContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL")));
}
// Exception
else
{ throw new ArgumentException("Not a valid database type"); }
}
Now we can do a singel migration
Add-Migration InitialCreate
Only edit every output of Add-Migration and add driver specific attributes:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Mytable",
columns: table => new
{
Id = table.Column<int>(nullable: false)
// Add for SQLite
.Annotation("Sqlite:Autoincrement", true)
// Add for MySQL
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn)
// Add for MSSQL
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn)
// Add for PostgreSQL
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
// Or other provider...
Name = table.Column<string>(maxLength: 50, nullable: false),
Text = table.Column<string>(maxLength: 100, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Mytable", x => x.Id);
});
}
EDIT:
or you use string ID "DatabaseGenerated"
so you would not have to edit migrationBuilder and the add migration is multiple providers capable without ".Annotation"
EXAMPLE Model:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WebApplication.Models
{
public class Mytable
{
// This generate a String ID
// No ID modification needed for providers
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
// ....
}
}
Now ready for Update-Database

You may want to consider a utility like AdaptiveClient. AdaptiveClient allows you to create a single DbContext with multiple provider-specific implementations of your services (MSSQL, MySQL, SQLite, etc). AdaptiveClient injects the correct implementation based on the connection string in use.
AdaptiveClient also allows you to inject transport-specific service implementations. For example many applications run both locally (same LAN as database server) and remotely (use WCF or REST). When running locally AdaptiveClient will inject an implementation of your service that talks directly to your database. This gives a ~10x performance improvement. When running remotely AdaptiveClient injects a WCF or REST implementation.
See also:
AdaptiveClient.EntityFrameworkCore
Demo Application
AdaptiveClient is available as a nuget package.
Disclaimer: I am the author of AdaptiveClient.

Seimann's answer is good but I found working with migrations to be a pain. I wanted little or no manual work to get it working. I found the easiest way was to create a separate assembly for each provider and add an implementation of IDesignTimeDbContextFactory.
Another solution is to create a design time assembly but selecting which provider to use for migrations turned out to be difficult, at least until this feature is implemented here. I tried the suggested method of setting an environment variable before executing the migrations but I found using compiler constants to select the correct provider to be easier.
I organized this by creating a shared project to be used by all providers. Here is an example implementation that pulls your main projects configuration settings. This class will support both methods explained above so it can be simplified depending on your needs.
#if DEBUG
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
namespace Database.DesignTime
{
public class ApplicationDbContextDesignTimeFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Path.GetFullPath(#"..\MainProjectDirectory"))
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json")
.Build();
// Determine provider from environment variable or use compiler constants below
var databaseProvider = Environment.GetEnvironmentVariable("DatabaseProvider");
#if SQLSERVER
databaseProvider = "SqlServer";
#endif
#if POSTGRESQL
databaseProvider = "PostgreSql";
#endif
var connectionString = configuration.GetConnectionString($"{databaseProvider}Connection");
var contextBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
switch (databaseProvider)
{
#if SQLSERVER
case "SqlServer":
contextBuilder.UseSqlServer(connectionString, dbOptions =>
{
dbOptions.MigrationsAssembly("Database.SqlServer");
});
break;
#endif
#if POSTGRESQL
case "PostgreSql":
contextBuilder.UseNpgsql(connectionString, dbOptions =>
{
dbOptions.MigrationsAssembly("Database.PostgreSql");
});
break;
#endif
default:
throw new NotSupportedException(databaseProvider);
}
return new ApplicationDbContext(contextBuilder.Options);
}
}
}
#endif
Then in your database migration project add the compiler constant for each provider. For example:
Database.SqlServer.csproj
<DefineConstants>SQLSERVER</DefineConstants>
Database.PostgreSql.csproj
<DefineConstants>POSTGRESQL</DefineConstants>
When you want to add migrations from within VS, open the Package Manager Console and select the migration project as the Default project. When executing the command, you need to specify the project containing the implementation of IDesignTimeDbContextFactory you want to use.
Add-Migration Initial -StartupProject "Database.SqlServer"
Now you can switch back to your main project and use it as normal. Just for reference this is my relevant appsettings.json and startup code.
{
"DatabaseProvider": "SqlServer",
"ConnectionStrings": {
"SqlServerConnection": "Server=(localdb)\\mssqllocaldb;Database=DatabaseName;Trusted_Connection=True;MultipleActiveResultSets=true",
"PostgreSqlConnection": "Host=host;Database=DatabaseName;User ID=Test;Password=secrectPass"
}
services.AddDbContext<ApplicationDbContext>(options =>
{
switch (Configuration["DatabaseProvider"])
{
case "SqlServer":
options.UseSqlServer(Configuration.GetConnectionString("SqlServerConnection"), dbOptions =>
{
dbOptions.MigrationsAssembly("Database.SqlServer");
});
break;
case "PostgreSql":
options.UseNpgsql(Configuration.GetConnectionString("PostgreSqlConnection"), dbOptions =>
{
dbOptions.MigrationsAssembly("Database.PostgreSql");
});
break;
}
});
There is another suggested way to accomplish this as explained here but I found creating derived classes means the migration will only work for instances of the derived class and not the base class. So you would need to specify the derived class type in AddDbContext. The other method mentioned requires manual work which I want to avoid.

Related

Can't create Entity Framework code-first migrations

I've been developing a .NET Core 6 console application (not ASP.NET) the last weeks and now I've tried to implement Entity Framework 6 migrations to it.
However, even though I reused some code from a working database model that used migrations, now I can't manage to make it work and I've also been struggling due to the lack of output from dotnet-ef.
For reasons I can't remember, the database project I reused code from used Design-Time DbContext creation. I don't know if that's my optimal way to make migrations but at least it managed to work on the previous project. I implemented the required IDesignTimeDbContextFactory<DbContext> interface the same way it was done previously:
public class MySqlContextFactory : IDesignTimeDbContextFactory<MySqlContext>
{
public MySqlContext CreateDbContext(string[] args)
{
DbContextOptionsBuilder optionsBuilder = new();
ServerVersion mariaDbVersion = new MariaDbServerVersion(new Version(10, 6, 5));
optionsBuilder.UseMySql(DatabaseCredentials.GetConnectionString(), mariaDbVersion);
return new MySqlContext();
}
}
public class MySqlContext : DbContext
{
public DbSet<Endpoint> EndpointsSet { get; set; }
private readonly string _connectionString;
public MySqlContext() : base()
=> _connectionString = DatabaseCredentials.GetConnectionString();
public MySqlContext(string connectionString) : base()
=> _connectionString = connectionString;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> Configurator.Configure(optionsBuilder, _connectionString);
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> Configurator.Create(modelBuilder);
}
public static void Configure(DbContextOptionsBuilder optionsBuilder, string connectionString)
{
ServerVersion mariaDbVersion = new MariaDbServerVersion(new Version(10, 6, 5));
optionsBuilder.UseMySql(connectionString, mariaDbVersion);
}
public static void Create(ModelBuilder modelBuilder)
{
IEnumerable<Type> types = ReflectionUtils.GetImplementedTypes(typeof(IEntityTypeConfiguration<>));
if (types.Any())
{
foreach (Type entityConfigurationType in types)
{
modelBuilder.ApplyConfigurationsFromAssembly(entityConfigurationType.Assembly);
}
}
else
{
Environment.Exit((int) EExitCodes.EF_MODEL_NOT_FOUND);
}
}
However, when I tried to create the first migration, I've been prompted with this absolutely non-descriptive output from the dotnet-ef tool:
PS> dotnet ef migrations add Init
Build started...
Build succeeded.
PS>
But no migrations were made nor anything changed in my project. So I decide to force dotnet ef to tell me more things by appending the --verbose flag on the PS command:
[...]
Build succeeded.
dotnet exec --depsfile F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.deps.json --additionalprobingpath C:\Users\pablo\.nuget\packages --runtimeconfig F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.runtimeconfig.json C:\Users\pablo\.dotnet\tools\.store\dotnet-ef\6.0.1\dotnet-ef\6.0.1\tools\netcoreapp3.1\any\tools\netcoreapp2.0\any\ef.dll migrations add Init -o Migrations\Init --assembly F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.dll --project F:\pablo\Documents\source\MyBot\MyBot.csproj --startup-assembly F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.dll --startup-project F:\pablo\Documents\source\MyBot\MyBot.csproj --project-dir F:\pablo\Documents\source\MyBot\ --root-namespace MyBot--language C# --framework net6.0 --nullable --working-dir F:\pablo\Documents\source\MyBot--verbose
Using assembly 'MyBot'.
Using startup assembly 'MyBot'.
Using application base 'F:\pablo\Documents\source\MyBot\bin\Debug\net6.0'.
Using working directory 'F:\pablo\Documents\source\MyBot'.
Using root namespace 'MyBot'.
Using project directory 'F:\pablo\Documents\source\MyBot\'.
Remaining arguments: .
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Found IDesignTimeDbContextFactory implementation 'MySqlContextFactory'.
Found DbContext 'MySqlContext'.
Finding application service provider in assembly 'MyBot'...
Finding Microsoft.Extensions.Hosting service provider...
No static method 'CreateHostBuilder(string[])' was found on class 'Program'.
No application service provider was found.
Finding DbContext classes in the project...
Using DbContext factory 'MySqlContextFactory'.
PS>
The first thing I thought I could search for was that CreateHostBuilder function the tool is searching but not retrieving. However, once again, all the documentation I could find was refer to ASP.NET applications, and programming patterns I'm not implementing in my bot application. My app does retrieve the services via Dependency Injection, custom made (maybe that's the reason of the line No application service provider was found. ?), but I didn't find a way to implement that CreateHostBuilder function without changing everything.
Just for adding the information, this is how I managed to create and configure the EF model with the non-migrations approach:
public static IServiceProvider GetServices(DiscordSocketClient client, CommandService commands)
{
ServiceCollection services = new();
services.AddSingleton(client);
services.AddSingleton(commands);
services.AddSingleton<HttpClient>();
services.AddDbContext<MySqlContext>(ServiceLifetime.Scoped);
return AddServices(services) // builds service provider;
}
private static async Task InitDatabaseModel(IServiceProvider provider)
{
MySqlContext? dbCtxt = provider.GetService<MySqlContext>();
if (dbCtxt == null)
{
Environment.Exit((int) EExitCodes.DB_SERVICE_UNAVAILABLE);
}
await dbContext.Database.EnsureDeletedAsync();
await dbContext.Database.EnsureCreatedAsync();
}
But unfortunately, my application is planned to interact with a database dynamically, so the Code-First configuring approach is not valid for me.
How can I solve this? Is an approach problem, or am I messing around with the custom non ASP.NET Dependency Injection provider? Thank you all
There is an issue with your IDesignTimeDbContextFactory. EF Core is trying to your this factory to create a MySqlContext.
public class MySqlContextFactory : IDesignTimeDbContextFactory<MySqlContext>
{
public MySqlContext CreateDbContext(string[] args)
{
// set up options
DbContextOptionsBuilder optionsBuilder = new();
ServerVersion mariaDbVersion = new MariaDbServerVersion(new Version(10, 6, 5));
optionsBuilder.UseMySql(DatabaseCredentials.GetConnectionString(), mariaDbVersion);
// *** this is the issue ***
// return default constructor W/O options (ie, UseMySql is never called)
return new MySqlContext();
}
}
You can add this constructor to your DbContext class:
public MySqlContext(DbContextOptions<MySqlContext> options)
: base(options)
{
}
and then return new MySqlContext(optionsBuilder.Options) from your factory.

Entity Framework Core migration - connection string

I'm having a problem to handle the DB connection string in conjunction with migrations.
I have 2 projects:
Domain
Application
The DbContext is in the Domain project, so this is the project I run migrations against.
The migrations concept enforces me to implement OnConfiguring in my DbContext and therein specify the database provider, eg:
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder.UseSqlServer("<connection string>");
}
My problem is that I don't want to use a hard coded connection string, for obvious reasons, and I cannot use ConfigurationManager to read it from the config file since the config file is in the application project.
All the examples I've seen involve either hard-coding the connection string or putting it in my ASP.NET Core application's settings files.
If you aren't using ASP.NET Core, or maybe, I don't know, don't want to have your local environment's database details committed to source control, you can try using a temporary environment variable.
First, implement IDesignTimeDbContextFactory like this (note that IDbContextFactory is now deprecated):
public class AppContextFactory: IDesignTimeDbContextFactory<AppContext>
{
public AppContextFactory()
{
// A parameter-less constructor is required by the EF Core CLI tools.
}
public AppContext CreateDbContext(string[] args)
{
var connectionString = Environment.GetEnvironmentVariable("EFCORETOOLSDB");
if (string.IsNullOrEmpty(connectionString))
throw new InvalidOperationException("The connection string was not set " +
"in the 'EFCORETOOLSDB' environment variable.");
var options = new DbContextOptionsBuilder<AppContext>()
.UseSqlServer(connectionString)
.Options;
return new AppContext(options);
}
}
Then, you can include the environment variable when you call Update-Database, or any of the other EF Core tools:
$env:EFCORETOOLSDB = "Data Source=(local);Initial Catalog=ApplicationDb;Integrated Security=True"; Update-Database
Here's how I do it, without a lot of extra code or craziness.
Project Structure:
AspNetCoreProject.Web
AspNetCoreProject.Data <-- DbContext here
My DbContext is set up with the constructor that allows you to inject the DbContextOptions
AspNetCoreProject.Data
public class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
}
In your application or web application, you set up your ConfigureServices normally.
AspNetCoreProject.Web / Startup.cs / ConfigureServices()
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("connection"))
Now, what about migrations? Well, I "trick" the Visual Studio UI into working as expected.
First, make sure your application (AspNetCoreProject.Web project with Startup.cs) is the start up project.
Second, open up your Nuget Package Manager Console. At the top of the Nuget PM> Console, there's a dropdown for 'Set Default Project', point this to your AspNetCoreProject.Data or project with the DbContext class.
Run your migration commands normally. add-migration init then update-database
Assuming your DbContext class has a constructor that accepts a parameter of type DbContextOptions, the dotnet ef commands have native support for this scenario - requiring no code changes nor additional configuration. Just use the "--startup-project" and "--project" parameters when creating and running migrations.
For example, let's say you have a "Application" project with your configuration and a separate project called "Domain" where the DbContext is implemented.
Context:
public class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
}
Startup:
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("connection"))
CLI command:
dotnet ef database update --startup-project Application --project Domain
We've had a same issue and there is a solution. :)
You have to implement IDbContextFactory<TContext>
When doing so you can read the connectionstrings from your appsettings.json. You can also use Add-Migration without errors, because overwriting OnConfigure() is obsolete then.
Sample implementation:
public class DomainContextFactory : IDbContextFactory<DomainContext>
{
public string BasePath { get; protected set; }
public DomainContext Create()
{
var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var basePath = AppContext.BaseDirectory;
return Create(basePath, environmentName);
}
public DomainContext Create(DbContextFactoryOptions options)
=> Create(options.ContentRootPath, options.EnvironmentName);
private DomainContext Create(string basePath, string environmentName)
{
BasePath = basePath;
var configuration = Configuration(basePath, environmentName);
var connectionString = ConnectionString(configuration.Build());
return Create(connectionString);
}
private DomainContext Create(string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
{
throw new ArgumentException($"{nameof(connectionString)} is null or empty", nameof(connectionString));
}
var optionsBuilder = new DbContextOptionsBuilder<DomainContext>();
return Configure(connectionString, optionsBuilder);
}
protected virtual IConfigurationBuilder Configuration(string basePath, string environmentName)
{
var builder = new ConfigurationBuilder()
.SetBasePath(basePath)
.AddJsonFile("constr.json")
.AddJsonFile($"constr.{environmentName}.json", true)
.AddEnvironmentVariables();
return builder;
}
protected virtual string ConnectionString(IConfigurationRoot configuration)
{
string connectionString = configuration["ConnectionStrings:DefaultConnection"];
return connectionString;
}
protected virtual DomainContext Configure(string connectionString, DbContextOptionsBuilder<DomainContext> builder)
{
builder.UseSqlServer(connectionString, opt => opt.UseRowNumberForPaging());
DomainContext db = new DomainContext(builder.Options);
return db;
}
DomainContext IDbContextFactory<DomainContext>.Create(DbContextFactoryOptions options)
=> Create(options.ContentRootPath, options.EnvironmentName);
}
How we use it:
public override IServiceResult<IList<Datei>> LoadAllData()
{
using (var db = this.DomainContextFactory.Create())
{
var files = db.Datei
.ToListAsync<Datei>();
return new ServiceResult<IList<Datei>>(files.Result, files.Result.Count);
}
}
sample config
{
"ConnectionStrings": {
"DefaultConnection": "Put your connectionstring here"
}
}
I was using OnConfiguring below with configured in Windows environment variable MsSql.ConnectionString and command for initial ef migration creation started to work: dotnet ef migrations add InitialCreate
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connectionString = Environment.GetEnvironmentVariable("MsSql.ConnectionString");
if(string.IsNullOrEmpty(connectionString))
throw new ConfigurationErrorsException("Sql server connection string configuration required");
if (!optionsBuilder.IsConfigured)
{
optionsBuilder
.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
}
To configure environment variable:
use Win + R hotkeys kombination to open Run command window
Type systempropertiesadvanced and hit Enter
On Advanced tab click Environment Variables
Click New... button
In Variable name field type MsSql.ConnectionString
In Variable value field type your connection string value
Make sure console(and any program that starts console) is restarted after new variable addition and before running dotnet ef related commands
I have my DBContext in my console app and was using a ctor with few parameters (such as connection string etc), since EF Core Migrations was using the default parameter less ctor and hence the connection string wasn't being populated I had the migrations failing.
Just added code to get the connection string from ConfigurationBuilder within my default ctor to by pass this.
Was only playing around with console app and EF Core so this works for me for now.

How to set Entity Framework Core migration timeout?

I'm using the latest (1.0.0) version of EF Core. I have a migration to run on a quite big database.
I run:
dotnet ef database update -c ApplicationDbContext
And get:
Timeout expired. The timeout period elapsed prior to completion of
the operation or the server is not responding.
In the connection string I explicitly set the timeout like so:
Connect Timeout=150000
Unfortunately, it didn't help. How should I do this?
The error message you are getting is for a Command timeout, not a connection timeout.
UPDATE
As mentioned by Pace in comments, since EF Core 2.0 you are able to use IDesignTimeDbContextFactory to change the behaviour of your context when it is being created by tooling at design time such as happens with Migrations.
Create a separate class in your project that implements the IDesignTimeDbContextFactory interface and use the DbContextoptionsBuilder to configure the behaviour you want - in this case, setting the command timeout value to 600 seconds:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace EFCoreSample.Model
{
public class SampleContextFactory : IDesignTimeDbContextFactory<SampleContext>
{
public SampleContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<SampleContext>();
optionsBuilder.UseSqlServer(#"Server=.\;Database=db;Trusted_Connection=True;",
opts => opts.CommandTimeout((int)TimeSpan.FromMinutes(10).TotalSeconds));
return new SampleContext(optionsBuilder.Options);
}
}
}
Make sure that your existing DbContext has a constructor that takes a DbContextOptions object as a parameter:
public AdventureContext(DbContextOptions options) : base(options){}
When the tooling runs the migration, it looks first for a class that implements IDesignTimeDbContextFactory and if found, will use that for configuring the context. Runtime behaviour is not affected.
Original Answer No Longer Applies
There is no way to set the CommandTimeout on a context when using EF commands. But you can set it globally in the constructor, and then remove it later if you don't need to keep it:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext()
{
Database.SetCommandTimeout(150000);
}
}
You can set the timeout for migration only by setting the timeout on the context before calling the Migrations method:
using (var context = new DispatchingDbContext(_configuration))
{
context.Database.SetCommandTimeout(300);
await context.Database.MigrateAsync().ConfigureAwait(false);
}
Set timeout for migrations ef .netcore
You can do it also in the constructor of your database context class.
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
Database.SetCommandTimeout(150000);
}
Using Entity Framework 6 (NOT CORE!), I set a longer timeout for migrations using the DbMigrationsConfiguration.CommandTimeout property.
Like this:
In my Global.asax.cs:
protected void Application_Start()
{
DatabaseMigrationConfig.Register();
//etc
}
My DatabaseMigrationConfig Class:
public class DatabaseMigrationConfig
{
internal static void Register()
{
using (var context = new MyContext(Config.ConnectionStringMigrations))
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext,
Migrations.Configuration>());
context.Database.Initialize(false);
}
}
}
My Migrations.Configuration class:
using System.Data.Entity.Migrations;
internal sealed class Configuration : DbMigrationsConfiguration<MyContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = false;
CommandTimeout = 360;// <----- 6 minute timeout!
}
}
References:
Migrations: timeout error in Update-Database commands
DbMigrationsConfiguration.CommandTimeout Property
Note that I also use a different connection string during migrations - the user has higher permissions than the website and the connection timeout is longer. See this question - How to use a different connection string (but same database) for migrations
You can generate the migration SQL script and run it on your own directly on the SQL server using this command:
dotnet ef migrations script [Baseline migration]
This way you won't be limited to timeout limitations.
More info can be found here.
To generate this script for Entity Framework 6, use:
Update-Database -Script -SourceMigration: [Baseline migration]
I improved Mike Brind's answer by reading the connection string from the appsettings file:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace EFCoreSample.Model
{
public class SampleContextFactory : IDesignTimeDbContextFactory<SampleContext>
{
public SampleContext CreateDbContext(string[] args)
{
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
var settingsFileName = $"appsettings.{env}.json";
if (!File.Exists(settingsFileName))
{
settingsFileName = "appsettings.json";
}
var connectionString = new ConfigurationBuilder().AddJsonFile(settingsFileName).Build().GetConnectionString("GlobalConnection");
var optionsBuilder = new DbContextOptionsBuilder<SampleContext>();
optionsBuilder.UseSqlServer(connectionString,
opts => opts.CommandTimeout((int)TimeSpan.FromMinutes(10).TotalSeconds));
return new SampleContext(optionsBuilder.Options);
}
}
}
Small modification to Patrick Koorevaar's answer to account for layered appsettings.json files...
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args)
{
var builder = new ConfigurationBuilder();
var settingsFileName = "appsettings.json";
if (File.Exists(settingsFileName))
{
builder.AddJsonFile(settingsFileName);
}
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
var envSettingsFileName = $"appsettings.{env}.json";
if (File.Exists(envSettingsFileName))
{
builder.AddJsonFile(envSettingsFileName);
}
var connectionString = builder.Build().GetConnectionString("MyConnString");
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseSqlServer(connectionString,
opts => opts.CommandTimeout((int)TimeSpan.FromMinutes(10).TotalSeconds));
return new MyDbContext(optionsBuilder.Options);
}
}

Multiple dbContexts in ASP.NET vNext and EF7

I'm trying to get along with building web systems with ASP.NET vNext using MVC 6 and EF7. I'm looking at this tutorial: http://stephenwalther.com/archive/2015/01/17/asp-net-5-and-angularjs-part-4-using-entity-framework-7
On the page you'll see how to add a dbContext to a project and it's registered in the startup file like this:
// Register Entity Framework
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<MoviesAppContext>();
And the context class looks like this:
public class MoviesAppContext:DbContext
{
public DbSet<Movie> Movies { get; set; }
}
It all works good, but now I'm in need of adding an additional DbContext. Though I don't know how to register this additional context so that it will be used by EF and possible to use in my project.
Let's say I've created a new context like this:
public class MyNewSuper:DbContext
{
public DbSet<Model1> Model1 { get; set; }
public DbSet<Model2> Model2 { get; set; }
}
How do I go ahead to register it for use in my project then?
Important Note: The syntax for configuring the Entity Framework 7 services has changed since this post, which was accurate as of the last few beta rounds. The same idea should still apply to the new syntax though.
Here is what I've been doing:
services.AddEntityFramework().AddSqlServer()
.AddDbContext<DataContextA>(options => options.UseSqlServer(Configuration.Get("StorageSettings:SQLConnectionString")))
.AddDbContext<DataContextB>(options => options.UseSqlServer(Configuration.Get("StorageSettings:SQLConnectionString")));
where StorageSettings:SQLConnectionString is a connection string for a SQL Express database. Currently, I have both DataContextA and DataContextB sharing the same database, but you can keep them separate. If you want to keep using the Configuration method (which I wasn't aware of, pretty cool!) you could do something like this:
{
"Data": {
"DefaultConnectionA": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=ContextADatabase;Trusted_Connection=True;MultipleActiveResultSets=true",
"DefaultConnectionB": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=ContextBDatabase;Trusted_Connection=True;MultipleActiveResultSets=true"
}
},
"EntityFramework": {
"DataContextA": {
"ConnectionStringKey": "Data:DefaultConnectionA:ConnectionString"
}
"DataContextB": {
"ConnectionStringKey": "Data:DefaultConnectionB:ConnectionString"
}
}
}
with
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<DataContextA>()
.AddDbContext<DataContextB>();
Both DataContextA and DataContextB can be injected into your controller:
public class MyController: Controller {
public MyController(DataContextA dataA, DataContextB dataB) {
// Do stuff
}
}
First of all, in something like config.json you can add yur connection strings. Something like the following will work
"Data": {
"BlogData": { "ConnectionString": "Server=tcp:YourHostname.net,1433;Database=YourDatabaseName;User ID=YourDBUser#YourDomain;Password=YourPassword;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" },
"Identity": { "ConnectionString": "Server=tcp:YourHostname.net,1433;Database=YourDatabaseName;User ID=YourDBUser#YourDomain;Password=YourPassword;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" }
},
You then have two DBContexts. Let's say:
YourApp.AppDBContext and YourApp.AppIdentityDBContext
You need to include these at the top of your CS file of course.
using YourApp.AppDBContext;
using YourApp.AppIdentityDBContext;
In startup.cs for example, in the startup method, your configuration builder will look like this:
var builder = new ConfigurationBuilder()
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
In the ConfigureServices method you will add your DBContexts as follows:
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration["Data:BlogData:ConnectionString"]))
.AddDbContext<AppIdentityDbContext>(options =>
options.UseSqlServer(Configuration["Data:Identity:ConnectionString"]));
I hope this helps. Feel free to give me a shout if I can expand on this further.

How do you configure the DbContext when creating Migrations in Entity Framework Core?

Is there way that dependency injection can be configured/bootstrapped when using Entity Framework's migration commands?
Entity Framework Core supports dependency injection for DbContext subclasses. This mechanism includes allowing for configuration of data access outside of of the DbContext.
For example, the following would configure EF to persist to a SQL server using a connection string retrieved from config.json
ServiceCollection services = ...
var configuration = new Configuration().AddJsonFile( "config.json" );
services.AddEntityFramework( configuration )
.AddSqlServer()
.AddDbContext<BillingDbContext>( config => config.UseSqlServer() );
However, the migrations commands do not know to execute this code so Add-Migration will fail for lack of a provider or lack of a connection string.
Migrations can be made to work by overriding OnConfiguring within the DbContext subclass to specify the provider and configuration string, but that gets in the way when different configuration is desired elsewhere. Ultimately keeping my the migration commands and my code both working becomes undesirably complex.
Note: My DbContext lives in a different assembly than the entry point that uses it and my solution has multiple start-up projects.
If you are looking for a solution to configure context for migrations, you can use this in your DBContext class:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var connectionString = configuration.GetConnectionString("DbCoreConnectionString");
optionsBuilder.UseSqlServer(connectionString);
}
}
Remember to install those two packages to have SetBasePath and AddJsonFile methods:
Microsoft.Extensions.Configuration.FileExtensions
Microsoft.Extensions.Configuration.Json
Use IDesignTimeDbContextFactory
If a class implementing this interface is found in either the same project as the derived DbContext or in the application's startup project, the tools bypass the other ways of creating the DbContext and use the design-time factory instead.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace MyProject
{
public class BloggingContextFactory : IDesignTimeDbContextFactory<BloggingContext>
{
public BloggingContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlite("Data Source=blog.db");
return new BloggingContext(optionsBuilder.Options);
}
}
}
applied in Entity Framework 2.0, 2.1
Using IDbContextFactory<TContext> is now obsolete.
Implement this interface to enable design-time services for context types that do not have a public default constructor. Design-time services will automatically discover implementations of this interface that are in the same assembly as the derived context.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace MyProject
{
public class BloggingContextFactory : IDbContextFactory<BloggingContext>
{
public BloggingContext Create()
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer("connection_string");
return new BloggingContext(optionsBuilder.Options);
}
}
}
more info : https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext
If you're not happy with the hard-coded connection-string, take a look at this article.
As #bricelam commented this functionality does not yet exist in Entity Framework 7. This missing functionality is tracked by GitHub issue aspnet/EntityFramework#639
In the mean time, the easier workaround I found was to utilize a global state rather than hassle with subclassing. Not usually my first design choice but it works well for now.
In MyDbContext:
public static bool isMigration = true;
protected override void OnConfiguring( DbContextOptionsBuilder optionsBuilder )
{
// TODO: This is messy, but needed for migrations.
// See https://github.com/aspnet/EntityFramework/issues/639
if ( isMigration )
{
optionsBuilder.UseSqlServer( "<Your Connection String Here>" );
}
}
In Startup.ConfigureServices().
public IServiceProvider ConfigureServices( IServiceCollection services )
{
MyContext.isMigration = false;
var configuration = new Configuration().AddJsonFile( "config.json" );
services.AddEntityFramework( configuration )
.AddSqlServer()
.AddDbContext<MyDbContext>( config => config.UseSqlServer() );
// ...
}
(The configuration code actually lives in an Autofac Module in my case.)
In .NET Core since version 2.1 should be used IDesignTimeDbContextFactory because IDbContextFactory is obsolete.
public class FooDbContextFactory : IDesignTimeDbContextFactory<FooDbContext>
{
public FooDbContext CreateDbContext(string[] args)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<FooDbContext>();
var connectionString = configuration.GetConnectionString("ConnectionStringName");
builder.UseSqlServer(connectionString);
return new FooDbContext(builder.Options);
}
}
To combine the answers above this works for me
private readonly bool isMigration = false;
public MyContext()
{
isMigration = true;
}
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (isMigration)
{
optionsBuilder.UseSqlServer("CONNECTION_STRING");
}
}
I know this is a old question but I use the onConfiguring method and I don't have this problem
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(Startup.Configuration.Get("Data:DefaultConnection:ConnectionString"));
}
I just ask for an instance and run migrations in my Startup.cs file
public void ConfigureServices(IServiceCollection services)
{
// ASPNet Core Identity
services.AddDbContext<RRIdentityDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("RRIdentityConnectionString")));
}
And then in Configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var rrIdentityContext = app.ApplicationServices.GetService<RRIdentityDbContext>();
rrIdentityContext.Database.Migrate();
}
Note: There is no 'EnsureCreated' for the database. Migrate is supposed to create it if it doesn't exist, although how it is supposed to figure out the permissions I don't know - so I created an empty database.

Categories

Resources