Why doesn't scaffolding work as expected? - c#

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

Related

c# The ConnectionString property has not been initialized

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.

How can I get connectionstring from appsetting using ConfigurationManager?

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

Issue with Connection String with DBcontext in.Net core

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.

Net Core Application in Docker "LocalDB is not supported" without using a Local DB

I have a Net.Core Application running with a SQL-Database. The Connection string is in the environment variables.
On Local IIS the Application works fine.
Same Application as Docker-Container got following Error
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HLPO85V83VNO", Request id "0HLPO85V83VNO:00000001": An unhandled exception was thrown by the
application.
System.PlatformNotSupportedException: LocalDB is not
supported on this platform.
at
System.Data.SqlClient.SNI.LocalDB.GetLocalDBConnectionString(String
localDbInstance)
at
System.Data.SqlClient.SNI.SNIProxy.GetLocalDBDataSource(String
fullServerName, Boolean& error)
environment Variables for Docker:
"DB_CONNECTION": "Server=hc-XXX; Database=IB33DB-Core_XXX; User Id=sa;Password=XXX"
Here is the setup in the Network
Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ib.api_core.Data;
using Microsoft.EntityFrameworkCore;
using ib.api_core.Models;
namespace ib.api_core
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
string dbConnection = Environment.GetEnvironmentVariable("DB_CONNECTION");
Console.WriteLine("Env var DB Connection: "+dbConnection);
//Verweis auf den Datenbank Kontext
services.AddDbContext<ibContext>(options =>
options.UseSqlServer(dbConnection));
//Verweis auf den Datenbank Kontext
// services.AddDbContext<ibContext>(options =>
// options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
//1. Alle Anfragen in der Konsole loggen
app.UseMiddleware<RequestResponseLoggingMiddleware>();
//2. Prüfe Login und Berechtigung der Anfrage
app.UseMiddleware<AuthenticationMiddleware>();
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
ibContext.cs
using System;
using Microsoft.EntityFrameworkCore;
namespace ib.api_core.Models
{
public partial class ibContext : DbContext
{
public ibContext()
{
}
public ibContext(DbContextOptions<ibContext> options)
: base(options)
{
}
public static ibContext GetContext()
{
var optionsBuilder = new DbContextOptionsBuilder<ibContext>();
optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("DB_CONNECTION"));
return new ibContext(optionsBuilder.Options);
}
[...]
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("DB_CONNECTION"));
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
....
The error message means that at some point, your code tries to access the database using a connection string configured for LocalDB. I suspect that in your appsettings.json, there is a connection string that uses LocalDB that is used at some point.
From the code samples I cannot spot the reason for that. Maybe some code bypasses the environment variable and reads the connection string from a configuration file or the container runs an old version of the code.
However, in a project based upon this sample I was able to override the connection string in the container:
The following code uses .NET Core configuration to get the connection string. The advantage of this approach is that you can provide the connection string in several ways and also override it when running the container.
Context
The context that is used in the sample is a simple one:
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
Setup
In startup.cs, the context is registered and the connection string is retrieved using .NET Core configuration:
var connection = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<BloggingContext>
(options => options.UseSqlServer(connection));
Dev configuration
For development, a LocalDB is used that is configured in appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.AspNetCore.NewDb;Trusted_Connection=True;ConnectRetryCount=0"
}
}
Overriding the connection string for the container
When running the container, an environment variable named ConnectionStrings:DefaultConnection overrides the connection string from the appsettings file.
For debugging, I inserted an environment variable in launchsettings.json:
...
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_URLS": "https://+:443;http://+:80",
"ASPNETCORE_HTTPS_PORT": "44330",
"ConnectionStrings:DefaultConnection": "Data Source=MY_IP\\MY_INSTANCE;Initial Catalog=TestDb;User Id=MY_USER;Password=MY_PWD"
},
"httpPort": 10000,
"useSSL": true,
"sslPort": 44330
}
...
When running in IIS Express, the connection string from appsettings.json is used, when running in a container, the SQL Server instance on the host is accessed as configured in the environment variable.
It's a bit too late. But maybe it will help someone who faces this problem.
You can use host.docker.internal in ConnectionString which will resolve to your host IP.
For example (1433 - port sqlServer uses)
"Server=host.docker.internal,1433;Database=yourDatabaseName;Integrated Security=False;MultipleActiveResultSets=true;User=DbUser;Password=DbPassword;"

Dependency Injection with configuration in .net core

I am trying to inject dependency by getting configuration in class in .net core project. The class where I am trying to inject dependency is in another project. But somehow I am not able to get the values from config file in injected dependency.
Below is my code
In below DBContext I need to obtain value from configuration, where i have used DI of DBConfiguration class.
public class DBContext : DbContext
{
private readonly DBConfiguration _dBConfiguration;
public DBContext(DBConfiguration dBConfiguration)
{
_dBConfiguration = dBConfiguration;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_dBConfiguration.ConnectionString);
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
And my StartUp.cs file in web api
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<DBConfiguration>();
services.AddEntityFrameworkSqlServer().AddDbContext<DBContext>();
services.AddOptions();
services.Configure<DBConfiguration>(Configuration.GetSection("DBConfiguration"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
and my appsettings.json file
{
"DBConfiguration": {
"ConnectionString": "Server=myserver;Database=BaseProjectDB;Trusted_Connection=True;MultipleActiveResultSets=true",
"ApplicationName": "WebAPI"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
Any help on this appreciated !
You seem to be using DBConfigurationOptions in your StartUp file, while you're injecting DBConfiguration in your DBContext.
This is how I currently use my configuration:
public class Startup
{
private readonly IConfiguration _configuration;
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
_configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<DBConfigurationOptions>(_configuration.GetSection("DBConfiguration"));
}
}
and then it's injected as:
public class DBContext : DbContext
{
private readonly DBConfigurationOptions _dBConfiguration;
public DBContext(IOptions<DBConfigurationOptions> dBConfiguration)
{
_dBConfiguration = dBConfiguration.Value;
}
}
Why don't you configure the db directly in the place you also have the configuration?
In your DBContext class (btw you should probably choose a better name for that) you just need to expose a constructor like this, no need for overriding OnConfiguring or anything like that.
This class can be in any assembly you want.
class DBContext : DbContext
{
public DBContext(DbContextOptions<DBContext> options) : base(options)
{
}
}
For the configuration you can just use the built in optionsBuilder-action (place inside the ConfigureServices method):
services.AddEntityFrameworkSqlServer()
.AddDbContext<DBContext>(optionsBuilder =>
optionsBuilder.UseSqlServer(
Configuration.GetSection("DBConfiguration").Get<DBConfiguration>().ConnectionString)
);
Currently the way you get the configuration can definitely be improved. For example you could do something like this:
var DBConfig = Configuration.GetSection("DBConfiguration").Get<DBConfiguration>();
services.AddSingleton(DBConfig);// <- now you can inject that if you want but it's not necessary
// now we don't need to get the config here
services.AddEntityFrameworkSqlServer()
.AddDbContext<DBContext>(optionsBuilder =>
optionsBuilder.UseSqlServer(DBConfig.ConnectionString)
);
There are some other things you might want to improve like better naming for DBContext and not overriding members you don't have a specific implementation for (like you did with OnModelCreating).
Also for a next time you should include all the classes that aren't part of some sort of public API like your DBConfiguration class.
To receive the configuration, you have to modify the signature of your constructor in DBContext from
public DBContext(DBConfiguration dBConfiguration)
to
public DBContext(IOptions<DBConfiguration> dBConfiguration)
to receive the option properly (don't forget to add the namespace Microsoft.Extensions.Options).
Additionally, you need to define the DBConfiguration class somewhere with the properties you have in your configuration file.

Categories

Resources