Can't create Entity Framework code-first migrations - c#

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.

Related

How to configure Entity Framework in a separate project?

I have an ASP.NET MVC application EducationalCenter with the following structure of projects:
DbContext file is EducationalCenterContext.cs in the Data project and looks as follows:
public sealed class EducationalCenterContext: DbContext
{
public DbSet<Student> Students { get; set; }
public EducationalCenterContext( DbContextOptions<EducationalCenterContext> options)
: base(options)
{
Database.EnsureCreated();
}
}
And in Startup.cs file, the dbContext configured as follows in ConfigureService():
services.AddDbContext<EducationalCenterContext>
(options => options.UseSqlServer("Server=localhost;Database=EducationalCenterDb;Trusted_Connection=True;MultipleActiveResultSets=true"));
This is my working version to which I came by fixing errors when try to add-migration. However it seems awful to me that my web app has project reference to the Data project.
What was my first idea:
In appsettings.json I created this section :
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=EducationalCenterDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
Then I created AppSettings class in the Common project:
public class AppSettings
{
public string ConnectionString { get; set; }
}
Then I try to pass ConnectionString in DAL via DI:
services.Configure<AppSettings>(Configuration.GetSection("ConnectionStrings"));
And created EducationalDbContext.cs:
public sealed class EducationalCenterContext: DbContext
{
private readonly string _connectionString;
public DbSet<Student> Students { get; set; }
public EducationalCenterContext( IOptions<AppSettings>, DbContextOptions<EducationalCenterContext> options)
: base(options)
{
_connectionString = app.Value.ConnectionString;
Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}
But when I try to add-migration via PM Console, I ran into this error:
Could not load assembly 'EducationalCenter.Data'. Ensure it is referenced by the startup project 'EducationalCenter'
Then I added project reference and ran into the next error:
Unable to create an object of type 'EducationalCenterContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
Then I added services.AddDbContext<> in Startup.cs and came to the working version which I mentioned above.
So...
Is it normal that my web app has reference to the data access project?
Is it possible to configure EF in the Data project and ensure normal separation between DAL, BLL and web app?
Putting the context and configuration in a separate project is fine.
You got the first error because "Education Center" was set as start up project but did not have reference to data project.
The second error is because the migration builder needs some connection information in the data project to resolve the connection (to compare EF state and database state) to determine what changes are needed.
First add reference in your data project to:
Microsoft.EntityFrameworkCore.Design
Then add a context factory in your data project that migration console command will discover:
internal class MyContextFactory : IDesignTimeDbContextFactory<MyContext>
{
public MyContext CreateDbContext(string[] args)
{
var dbContextBuilder = new DbContextOptionsBuilder<MyContext>();
var connString = "myconnection string";
dbContextBuilder.UseSqlServer(connString);
return new MyContext(dbContextBuilder.Options);
}
}

Unable to create an object of type 'MyContext'. For the different patterns supported at design time

I have ConsoleApplication on .NET Core and also I added my DbContext to dependencies, but howewer I have an error:
Unable to create an object of type 'MyContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
I've added: var context = host.Services.GetRequiredService<MyContext>();
Also I've added private readonly DbContextOptions<MyContext> _opts; in my Post Class:
using (MyContext db = new MyContext(_opts))
{
db.Posts.Add(postData);
db.SaveChanges();
}
This how I added service:
.ConfigureServices((context, services) =>
{
services.Configure<DataOptions>(opts =>
context.Configuration.GetSection(nameof(DataOptions)).Bind(opts)
);
services.AddDbContext<MyContext>((provider, builder) =>
builder.UseSqlite(provider.GetRequiredService<IOptions<DataOptions>>().Value.ConnectionString)
);
});
And this is my Context:
public sealed class MyContext : DbContext
{
private readonly DbContextOptions<MyContext> _options;
public DbSet<PostData> Posts { get; set; }
public DbSet<VoteData> Votes { get; set; }
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
_options = options;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlite("ConnectionString");
}
}
}
I tried add-migration and has this error
What I do wrong?
I Resolved this by just adding a plain constructor to my Context
public class DataContext : DbContext
{
public DataContext()
{
}
public DataContext(DbContextOptions options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (!options.IsConfigured)
{
options.UseSqlServer("A FALLBACK CONNECTION STRING");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
I came across this problem today. In my case, the SqlDbContext was in a separate ASP.Net Core 3.1 class library project, and I was trying to setup migrations using the dotnet CLI from that project's root folder. The main web application, which is the default project, contains the connection string configuration inside the appsettings.json and the startup configurations therefore I had to specify the startup project path using the -s switch as follows.
>dotnet ef migrations add initialcreation -s ..\MyWebApp\MyWebApp.csproj
-s, short for startup project, is a quick alternative to implementing IDesignTimeDbContextFactory when the DbContext is in a different project than the web application project.
The quick solution to the problem is to implement the default constructor in your context
public MyContext() : base()
{
}
The problem with this solution is that you will have to enter the connection string in the 'OnConfiguring' function explicitly, which is not recommended
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlite("ConnectionString");
}
}
I've had same problem as You. Maybe it was not for a Console Application but error was the same. So i thought that it is worth to share with my answer. I was using NET Core 3.0 and to fix the problem I have to change the IHostBuilder into IWebHost and then everything was fine. The problem was in class Program.
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
into
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
Kindly check your connection string also in appsetting,json.
Hade the same problem with NET Core 3.1.
Needed to add one more constructor too solev this.
Why I do not know.
Never hade to do it in the past.
public DataContext()
{
}
public DataContext(DbContextOptions<DataContext> options)
: base(options)
{ }
This applies to ASP .NET or .NET console applications using .NET Core 3.1.
In my case it was ASPNET Core and I had the same reported problem after upgrading my application from 2.1 to 3.1. The answer provided by #Matt lead me to a solution that works and allows me to continue using the new Generic Host. The Web Host remains only for backward compatibility.
The documentation for Generic Host and Design-time DbContext Creation both state what needs to happen.
Your program.cs must have a CreateHostBuilder method with a signature exactly as documented. This is because the framework attempts to resolve it using Program.CreateHostBuilder(). The signature must be:
public static IHostBuilder CreateHostBuilder(string[] args)
This is what caught me out, I initially had it named CreateWebHostBuilder, a 2.1 convention; and then I didn't have the args parameter defined. Fixing these two issues immediately resolved my add-migration ... error.
The Design-time DbContext Creation documentation is quite helpful detailing how the framework attempts to resolve the DbContext and explains why other suggestions here work the way they do, e.g. parameterless constructor.
Ensure you have added DbContext to your services in Startup class
In my case at .Net 7 the problem was at the program.cs.
AddDbContext was after WebApplication.Build() and it should be before Build (just a mistake).
Incorrect:
using var app = builder.Build();
services.AddDbContext<DatabaseContext>(opt => opt
.UseSqlServer("<Connection-String>"));
Correct:
services.AddDbContext<DatabaseContext>(opt => opt
.UseSqlServer("<Connection-String>"));
using var app = builder.Build();
In my case, I had two startup project in my solution, so setting the project that has the connection string as the only startup project fixed the issue
I resolve this issue by this way:
public DbSet<PostData> Posts { get; set; }
public DbSet<VoteData> Votes { get; set; }
public MyContext(DbContextOptions options) : base(options) { Database.EnsureCreated(); }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(MyContext).Assembly);
}
In my case problem was that I has multiple startup projects selected. selected one and it was fixed
In my case with Net Core 3.1 (PostgreSQL) Solved it by removing "AddUserStore"
services.AddIdentity<User, IdentityRole<int>>()
.AddEntityFrameworkStores<MyDBContext>()
//.AddUserStore<MyDBContext>()
.AddDefaultTokenProviders();
I had this problem in .net core6.
in program.cs, you should put var app = builder.Build(); after Services.AddDbContext and connectionString.
My problem was solved
This is not an answer for this specific question's code, but this is for the error message.
A possible fix would be the order of constructors. If your DbContext has multiple constructors, make sure that the empty constructor is on top
In my case, my database connection string was wrong, I gave wrong database password, which caused this problem. So, check your connection string in appsettings, json as well.
Make sure to set your web or api project as Set as Startup Project. Do not use multiple startup project.
In Package Manager Console, set your data access layer (if any) as a default project
Then run the command again
If someone still uses this alternative to know why it gives them this error.
It is because there is an error in the configurations of the keys or constraints of the tables.
You can use dotnet ef database update --verbose, to know the error that was generated with the migrations.

Running EF Core with SQLite in memory - No Database Provider has been configured

I am writing an ASP.Net Core Web API using EF Core with SQL server and StructureMap for IOC.
The library projects are all .NetStandard2.0 while the API and Unit Tests are NetCoreApp2.0.
For the unit tests I'm running XUnit and swapping out SQL Server for an in memory SQLite db as it provides full referential integrity.
I'm still using the same IOC setup as my main code but I pass in a list of Structure map registries to allow me to substitute in a SQLite context factory rather than the sql one.
This is a trimmed down version of my DbContext:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions options) : base(options)
{
}
}
and this is my test setup code:
public IServiceProvider ServiceProvider { get; set; }
public MyServiceTests()
{
var services = new ServiceCollection();
var dataRegistry = new Registry();
dataRegistry.For<IContextFactory<MyDbContext>>().Use<SqlLiteContextFactory>();
if (SqlLiteContextFactory.Connection == null)
{
SqlLiteContextFactory.Connection = new SqliteConnection("DataSource=:memory:");
SqlLiteContextFactory.Connection.Open();
}
services
.AddEntityFrameworkSqlite()
//This is only here as some posts suggested this was needed, StartUp.cs for production site does not have this and works fine.
.AddDbContext<MyDbContext>(options => options.UseSqlite(SqlLiteContextFactory.Connection));
var registries = new List<Registry>
{
dataRegistry,
new CommandQueryRegistry(),
new ServiceRegistry(),
new TransformRegistry()
};
ServiceProvider = DependencyInjection.TestSetup(services, registries);
}
The IOC initialisation code is fairly basic:
public static IServiceProvider TestSetup(IServiceCollection services, List<Registry> registries)
{
var container = new Container();
var registry = new Registry();
registries.ForEach(r => registry.IncludeRegistry(r));
container.Configure(config =>
{
config.AddRegistry(registry);
config.ForSingletonOf<IHttpContextAccessor>().Use<HttpContextAccessor>();
config.Populate(services);
});
var serviceProvider = container.GetInstance<IServiceProvider>();
return serviceProvider;
}
This is the context factory code for my SQLite context factory, the only difference between this and the SQL one is that I have the static Connection property to ensure I don't lose the db once the context is disposed.
public class SqlLiteContextFactory : IContextFactory<MyDbContext>
{
public static SqliteConnection Connection;
private DbContextOptions<MyDbContext> CreateOptions(bool trackChanges)
{
var builder = new DbContextOptionsBuilder<MyDbContext>();
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseSqlite(Connection);
if (!trackChanges)
{
builder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
return builder.Options;
}
private MyDbContext CreateDbContext(bool trackChanges)
{
if (Connection == null)
{
Connection = new SqliteConnection("DataSource=:memory:");
Connection.Open();
}
var context = new MyDbContext(CreateOptions(trackChanges));
//Always keep context as most recent version in tests
context.Database.Migrate();
return context;
}
public MyDbContext CreateNonTrackedContext()
{
return CreateDbContext(false);
}
public MyDbContext CreateDbContext()
{
return CreateDbContext(true);
}
}
The Problem
My code runs fine when running the site, the SQL context factory creates the context and runs the migrate command to create the database no problem.
However, when I attempt to test any of my services through the unit tests, the context factory blows up when trying to run Migrate with the following:
System.InvalidOperationException : 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.
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, IDbContextOptions contextOptions, DbContext context)
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.GetRelationalService[TService](IInfrastructure`1 databaseFacade)
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
at API.Test.Utilities.SqlLiteContextFactory.CreateDbContext(Boolean trackChanges) in API.Test\Utilities\SqLiteContextFactory.cs:line 37
at API.Test.Utilities.SqlLiteContextFactory.CreateDbContext() in API.Test\Utilities\SqLiteContextFactory.cs:line 49
at API.CommandQueries.Commands.MyCommand.AddOrUpdate(MyModel model) in \API.CommandQueries\Commands\MyCommand.cs:line 21
at API.Services.MyService.Save(Model model) in API.Services\MyService.cs:line 40
at API.Test.MyTests.CanAdd() in API.Test\MyServiceTests.cs:line 47
I have tried every solution I can find to this problem. Adding in the .AddDbContext to the service collection. Making sure that both the test project and the context project have references to EntityFrameworkCore, EntityFrameworkCore.Relational and EntityFrameworkCore.Sqlite. Ensuring that the SQLite connection is kept alive and making sure the test set up uses .AddEntityFrameworkSqlite() on the ServiceCollection.
I've also tried swapping SQLite out for EF Cores InMemory Db with yet another context factory but that fails with exactly the same problem.
Has anyone else seen this issue before or am I using EF Core in some way that makes it incompatible with SQLite somehow?
I worked on this a while back. And I believe you need to run migrations AND keep a connection open. You are overwriting the connection static. Plus I'm not sure it is getting created before you use the connection.

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 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