I've been trying to create a migration and then update the database (using EF Core tools and SSMS to check the database). I've been struggling because I have different projects. The organization of my solution is the following:
I want to have the migration and the related DB interactions in VSC.Repo. This means that the context is in this project. Besides that, I have my connection string in the default appsettings, which is in VSC.API (different assembly). I've tried various ways of trying to get the connection string from there, but I always get the following error when I run the "dotnet ef database update" in the VSC.Repo project:
This is my context class:
public class DataContext : DbContext
{
private readonly string connectionString;
public DataContext()
{
}
public DataContext(DbContextOptions<DataContext> options, IConfiguration configuration) : base(options)
{
connectionString = configuration.GetSection("ConnectionStrings:DefaultConnection").Value;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString);
}
I can't figure out what I'm doing wrong. Any help would me much appreciated.
EDIT: With the hardcoded string it works perfectly fine, but this is bad practice and I don't want to implement this way.
appsettings.json:
"ConnectionStrings": {
"DefaultConnection": "server=localhost;database=vscDatabase;trusted_connection=true;TrustServerCertificate=True;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
program.cs:
WebApplicationBuilder? builder =
WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<DataContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//builder.AddRepoConfigurations();
var app = builder.Build();
Okay so #Panagiotis Kanavos found out that I was executing the command in the wrong project, more precisely, in one that did not have the program.cs, and this was the issue. When I executed in the correct one, it worked just fine.
Related
I am starting a web api which uses postgres as the backend.
I am on a team of developers that will each have a local db for development. How can I ensure that if a new developer comes on the team that their local db is created without manually creating it themself.
As of right now, we each created our local db's through command line and update our appsettings.Development.json to use our local db connection string (which is on the gitignore incase we use sensitive passwords).
It's annoying to have to manually set this portion up however. Is there anyway to just have a database be created on the fly? As in, right when they launch the API, the db should be created and the migrations applied onto it.
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Local": "Server=localhost;Port=5432;Database=mydb;User ID=xxx;Password=xxx;"
}
}
Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
UpdateDatabase(app);
// Other configurations
}
public virtual void UpdateDatabase(IApplicationBuilder app)
{
using var serviceScope = app.ApplicationServices
.GetRequiredService<IServiceScopeFactory>()
.CreateScope();
using var context = serviceScope.ServiceProvider.GetService<MyDbContext>();
context.Database.EnsureCreated();
context.Database.Migrate();
}
I created a asp.net core web api project that has Microsoft.EntityFrameworkCore.SqlServer, Microsoft.EntityFrameworkCore.Tools, and System.Configuration.ConfigurationManager. I ran the Scaffolding command Scaffold-DbContext "Data Source=DEX-LEP3LOXH0M5\\SQLEXPRESS;Initial Catalog=LibraryStore;Integrated Security=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models.
In Context.cs file that was created from the scaffolding cmd created this method:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
//#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
optionsBuilder.UseSqlServer("Data Source=DEX-LEP3LOXH0M5\\SQLEXPRESS;Initial Catalog=LibraryStore;Integrated Security=True");
}
}
when I ran the route path to the controller it worked perfectly I was able to see the records from the DB. But on that #warning message, it provided a link which I followed. In my appsettings.json file I added ConnectionStrings by doing:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Default": "Data Source=DEX-LEP3LOXH0M5\\SQLEXPRESS;Initial Catalog=LibraryStore;Integrated Security=True"
}
}
then my next step was to add the code in the ConfigureServices in the startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "LibraryWebAPI", Version = "v1" });
});
services.AddDbContext<LibraryStoreContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Default")));
}
then it said to change the OnConfiguring() in the Context.cs file that was created.
I did the following:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings["Default"].ConnectionString);
}
}
but then it generates the error message on the line optionsBuilder:
System.Configuration.ConnectionStringSettingsCollection.this[string].get returned null.
Is there a reason why it is returning null if the same connection string worked before following the link to add connection to connectionstring in appsettings?
including my controlller:
[HttpGet]
[Route("GetAllDetails")]
public IEnumerable<LibraryInfo> GetDetails()
{
using (var context = new LibraryStoreContext())
{
// get all library details
return context.LibraryDetails.ToList();
}
}
I did just find a more comfortable way to maintain generating migrations, where you don't have to run the command from the web project.
DbContext
internal class ContosoContext : DbContext
{
private readonly IConfiguration configuration;
public ContosoContext(IConfiguration configuration)
{
this.configuration = configuration;
}
public ContosoContext()
{
this.configuration = null;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
if (configuration == null)
{
// Only used when generating migrations
// Contoso.Data = name of the project where your migrations should reside
var migrationsConnectionString = #"Server=(localdb)\mssqllocaldb;Database=Contoso;Trusted_Connection=True;ConnectRetryCount=0";
optionsBuilder.UseSqlServer(migrationsConnectionString, options => options.MigrationsAssembly("Contoso.Data"));
}
else
{
optionsBuilder.UseSqlServer(configuration.GetConnectionString("Contoso"));
}
}
}
Registration of the DbContext
services.AddDbContext<ContosoContext>(options => {
options.UseSqlServer(Configuration.GetConnectionString("Contoso"));
});
When running the application (the web project), the constructor with parameters will be called.
When generating migrations from the Contoso.Data folder (dotnet ef migrations add myawesomemigration), the parameterless constructor will be called.
This drops the need of excessively specifying paramters during the generation of database migrations:
dotnet ef migrations add AddIdentity
The .Net Core application uses appsettings.json instead of web.config file.
Because .Net Core applications are self hosted and can almost run on any platform, they are no longer have to be hosted on IIS. The .Net Core application settings are stored in a Json format (appsettings.json) by default while .Net Framework application configurations are stored in a web.config file in XML format
This is why it shouldn't work with your appsettings.json when using ConfigurationManager
From the guid you sent they say to do the change in protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
But this is said not on .net core applications
Currently i am working on a .Net core 3.1 App. I am using below code in the startup to Add the Dbcontext.
services.AddDbContext<sampleContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
As this is the code first approach i have below code in the Dbcontext
public class sampleContext: DbContext
{
public sampleContext()
{
}
public sampleContext(DbContextOptions<sampleContext> options) : base(options){ }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("DefaultConnection", EnvironmentVariableTarget.Process));
}
}
}
When i am running the API, its working as expected as optionsBuilder.IsConfigured=true.
Appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Initial Catalog=sampleDb; Integrated Security=true;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"UploadFAUrl": "http://localhost:7071/Api"
}
Coming to issue:-
When i am running the CLI command ADD-MIGRATION sampleCongetting **Value cannot be null. (Parameter 'connectionString')**
Whys is so? As we will be moving to different env, we may need to run this command. Atleast in local, we need to run the command. How to fix this issue? Referred some of the question but non helped. PLease suggest if i am missing anything.
The EF Core command line tools, will attempt to locate all required services and configuration, based on your current project. By looking for your CreateHostBuilder method, and calling it;
public static IHostBuilder CreateHostBuilder(string[] args) => ...
You shouldn't need to override OnConfiguring. But you may need to provide an explicit --startup-project command line parameter.
Do you have multiple projects?
Mark the project Set as startup project which contains Appsettings.json.
I'm using multiple examples on how to set up my MySql service but in almost every one (including the Microsoft), they are using a hard coded connection string.
This is my startup:
services.AddDbContext<DbContext>(options =>
{
options.UseMySql(configuration.GetConnectionString("DefaultConnection"),
mysqlOptions =>
{
mysqlOptions.ServerVersion(new ServerVersion(new Version(8, 0, 18)));
});
});
And whenever I try to Update-Database I see an error telling me that there's no password setted
MySql.Data.MySqlClient.MySqlException (0x80004005): Access denied for user 'root'#'localhost' (using password: YES).
After looking around a bit I found an overrideable method called OnConfiguring, but I want to set the connection string in a dynamic way, cause If I change my environment, I want that string to change too.
This is where I found almost every example with hardcoded strings (ex. https://dev.mysql.com/doc/connector-net/en/connector-net-entityframework-core.html)
I am addressing this portion of your question:
"After looking around a bit I found an overrideable method called OnConfiguring, but I want to set the connection string in a dynamic way, cause If I change my environment, I want that string to change too."
In order to solve getting the connection string into the OnConfiguring method inside your DbContext class I simply passed IConfiguration into the DbContext constructor like this.
private readonly IConfiguration _config;
public CoreDbContext(IConfiguration config)
{
_config = config;
}
public CoreDbContext(DbContextOptions<CoreDbContext> options,
IConfiguration config) : base(options)
{
_config = config;
}
Then in the OnConfiguring method I pulled the connection string from that config object injected at construction. Like this:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(_config.GetConnectionString("YourKeyValueForConnStringInAppSettings.json"));
}
}
Finally here are details of my environment / Project:
Windows 10 machine
Visual Studio 2022 (Version 17.0.4)
ASP.NET Core Web API
.NET 6.0
Configure for HTTPS
Use controllers checked
Enable OpenApi support
Authentication type at time of writing is None
You need to init configuration if not configured.
This can be done by overriding the OnConfiguring method in DbContext.
This way you can have global application configuration that will be pulled when application is ran. And use local configuration when doing Db migration.
Try this
public class YourContext: DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (options.IsConfigured == false)
options.UseMysql({YOUR_CONNECTION_STRING});
}
}
Based on the comment, I think you want to separate the EF Core related classes to a different assembly.
In the UseMySql there is an option to configure MigrationsAssembly. You can get more details here
Here is pseudo-code for SQL Server.
services.AddDbContext<EFCoreDemoContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
assembly => assembly.MigrationsAssembly(typeof(EFCoreDemoContext).Assembly.FullName));
});
in configuration file, you should have a ConnectionStrings that points to your database with other options.
"ConnectionStrings": {
"DefaultConnection": "server=127.0.0.1;uid=root;pwd=12345;database=test"
},
When you use configuration.GetConnectionString, you are looking up a value from the configuration section.
Try this at startup.cs
public class Startup
{
private readonly IConfiguration _config;
public Startup(IConfiguration config)
{
_config = config;
}
// This method gets called by the runtime. Use this method to add services to the
container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MainContext>(options =>
options.UseOracle(_config["CONNECTION_STRING"]));
}
}
And do that at your Context ctor
public MainContext(DbContextOptions<MainContext> options) : base(options) { }
I am trying to scaffold and I get the following error:
There was an error running the selected code generator: 'No parameterless constructor defined for type 'MvcProduct.Data.MvcProductContext'.'
Here you can see an image of it:
The following is my MvcProductContext:
using Microsoft.EntityFrameworkCore;
using MvcProduct.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MvcProduct.Data
{
public class MvcProductContext : DbContext
{
public MvcProductContext(DbContextOptions<MvcProductContext> options)
: base(options)
{
}
public DbSet<Product> Product { get; set; }
}
And the appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcProductContext": "Server=(localdb)\\mssqllocaldb;Database=MvcProductContext-1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
ConfigureServices method in Startup.cs file:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<MvcProductContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MvcProductContext")));
}
I have also tried to add a a second constructor in MvcProductContext class. (Something which I would like to avoid and don't want to do) A second contructor without any parameter. But if I do that I just get another error which says:
There was an error running the selected code generator: 'No database provider has been configured for this DbContext. A provider can be configured bu overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbCotnext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
Microsoft is does the same. They are scaffolding an MVC controller with views, using Entity Framework. They are doing it without adding a second constructor in their MvcMovieCOntext class. Their MvcMovieContextClass corresponds to my MvcProductContext class.
Any help would be appreciated.
Just add a IDesignTimeDbContextFactory implementation to your project and try scaffolding again. It will take care of instantiating your DbContext.
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<MvcProductContext>
{
public MvcProductContext CreateDbContext(string[] args)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appSettings.json")
.Build();
var builder = new DbContextOptionsBuilder<MvcProductContext>();
var connectionString = configuration.GetConnectionString("MvcProductContext");
builder.UseSqlServer(connectionString);
return new MvcProductContext(builder.Options);
}
}
In .net core 6 :
Install 4 packages:
1.Microsoft.EntityFrameworkCore.SqlServer
2.Microsoft.EntityFrameworkCore.Tools
3.Microsoft.EntityFrameworkCore.Design
4.Microsoft.VisualStudio.Web.CodeGeneration.Design
Scaffold the data base and create the DbContext and Entities by scaffold command or ef core power tools vs extension.
Inject your data base settings in program.cs :
builder.Services.AddRazorPages();
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("CS"));
});
Add your connection string to appsettings.json :
"AllowedHosts": "*",
"ConnectionStrings": {
"CS": "Data Source=.\\SQLEXPRESS;Initial Catalog=Db1;Integrated Security=True;MultipleActiveResultSets=True"
}