Use settings.json on a Net Core Class Library with EF Core migrations - asp.net-core

On a NET Core 2.1 Class Library I have a Entity Framework Core 2.1 DbContext:
public class AppContext : IdentityDbContext<User, Role, Int32> {
public AppContext(DbContextOptions options) : base(options) { }
}
To run migrations on the Class Library I needed to add the following:
public class ContextFactory : IDesignTimeDbContextFactory<Context> {
public Context CreateDbContext(String[] args) {
DbContextOptionsBuilder builder = new DbContextOptionsBuilder<Context>();
builder.UseSqlServer(#"Server=localhost;Database=db;User=sa;Password=pass;");
return new Context(builder.Options);
}
}
With this I am able to run, on the class library commands like:
dotnet ef migrations add "InitialCommit"
dotnet ef database update
But how to move the connection string to a settings.json file in the Class Library?

IDesignTimeDbContextFactory, as its name implies, is strictly for development. There is generally no need to externalize the connection string, because it should be pretty static, even in a team environment. That said, if anything, you should store it in user secrets, because again, this is only for development. Using user secrets keeps the connection string out of your source control, so developers on your team don't step on each other's toes with each other's connection strings.
var config = new ConfigurationBuilder()
.AddUserSecrets()
.Build();
var connectionString = config.GetConnectionString("Foo");

The IDesignTimeDbContextFactory<> implementation is run via the EF utility process. This is a regular console application where you can use Console.Write() and Console.Read() to interact with the user performing migrations and updates. This allows you to prompt the user to enter their connection string at update-time.
public class Builder : IDesignTimeDbContextFactory<AppContext>
{
public AppContext CreateDbContext(string[] args)
{
Console.Write("Enter your connection string: ");
var conStr = Console.ReadLine();
var options = new DbContextOptionsBuilder<AppContext>().UseSqlServer(conStr).Options;
return new AppContext(options);
}
}

You can configure DbContextOptions in startup class and then inject it into context. Within Startup class you can obtain connection string from Configuration.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<Context>
(options=> options.UseSqlServer(Configuration["ConnectionString:DefaultConnection"]));
....
}
Add you connection string to appsettings.json:
"ConnectionString": {
"DefaultConnection": "your connection string"
},

Related

No database provider has been configured for this DbContext .NET Core with SQL Server

I have been banging my head against a wall with this one and have been googling to no avail.
I have just started a new ASP.NET Core MVC project, I have installed/updated my packages for these two to 2.2.0:
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
I have set the project to expect .NET Core 2.2.0 as well.
I am able to successfully add my table schemas with this command in Package Manager console to scaffold the Database, so I know the connection string is fine/working:
Scaffold-DbContext "SERVER=Server\Instance;DATABASE=Database;UID=user;PWD=password;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Tables Table1, Table2, Table3
The created model file, DatabaseDBContext.cs looks like this:
public partial class DatabaseDBContext : DbContext
{
public DatabaseDBContext()
{
}
public DatabaseDBContext(DbContextOptions<DatabaseDBContext> options)
: base(options)
{
}
}
This also contains a method that works to retrieve my scaffold data, but isn't considered safe for production use so I commented this out:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("SERVER=Server\\Instance;DATABASE=Database;UID=user;PWD=password;");
}
}
I added this same connection string to the appsettings.json file:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"DBConnString": "SERVER=Server\\Instance;DATABASE=Database;UID=user;PWD=password;"
}
}
I then added the DbContext to the startup.cs file:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddDbContext<DatabaseDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DBConnString")));
}
Trying to add a new scaffolded controller for one of the tables throws this error:
Finding the generator 'controller'...
Running the generator 'controller'...
Attempting to compile the application in memory.
Attempting to figure out the EntityFramework metadata for the model and DbContext: 'TableName'
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 object in its constructor and passes it to the base constructor for DbContext.
StackTrace:
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, IDbContextOptions contextOptions, DbContext context)
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.Internal.InternalAccessorExtensions.GetService[TService](IInfrastructure1 accessor)
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func1 factory)
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 object in its constructor and passes it to the base constructor for DbContext.
Has anyone got any clue what I am doing wrong here?
So I fixed but it in a really roundabout way. My new project was originally on an older version of .net core. I had updated the version but there must have been something it didn't like during the update. I created a new project and started it on 2.2.0, then it worked...
The code logic was sound above. Still needed the same packages:
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Startup.cs seems quite different, so maybe if anyone else sees this they could try updating the startup.cs code:
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.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<DatabaseDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DatabaseDBConnString")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Had to add a reference to startup.cs for this:
using Microsoft.EntityFrameworkCore;
That was needed for the AddDbContext method to resolve.
After doing this the scaffolding now works. So it's fixed, but it required me to start over to fix it.
I had the same problem and this solved it for me (setting UseSqlServer in OnConfiguring):
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
if (!builder.IsConfigured)
{
string conn = this.IsProduction ? Const.ProductionConnectionString : Const.LocalDBConnectionString;
builder.UseSqlServer(conn);
}
base.OnConfiguring(builder);
}
After battleing with this issue I've encounter the solution for it here
https://github.com/aspnet/EntityFrameworkCore/issues/12331
The problem was that Add-Migration was expecting the CreateWebHostBuilder
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
Before my public static void Main(string[]) was running the WebHost without the static CreateWebHostBuilder and that after I added the Static function then it worked.
I had this problem after I've inlined Program.CreateWebHostBuilder into Program.Main. Had to extract it back.
It was wired but fixed this issue by updating the framework version of the project solution. For example, I created one core repo on 3.0 and later installed the latest version 3.1 on the system so it was expecting to be updated with the latest version. I changed it and it worked!
Try add this 3rd constructor:
public DatabaseDBContext()
{
}
public DatabaseDBContext(DbContextOptions<DatabaseDBContext> options)
: base(options)
{
}
public DatabaseDBContext(DbContextOptions options)
: base(options)
{
}
Then chuck a breakpoint in each one just so you are sure which one is actually getting used.

ASP.NET Core how to init connection string in first run-time?

I am building an enterprise application and i want for the first time the use run the website to be able to choose the SQL server to connect to, and the Database in that server. after that the application always run on that configurations like normal asp.net core app do.
I know that connection string is stored in appsettings.json so i was thinking about changing it's value at run-time after choosing this configuration, but after searching a lot i think this is not possible.
What do suggest is there a way to do this ?
Set reloadOnChange to true when you are adding appsettings.json to ConfigurationBuilder:
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
then create a class for connection string
public class ConnectionString
{
public string Default {get; set;}
}
And in ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.Configure<ConnectionString>(Configuration.GetSection("ConnectionStrings"));
}
Now this configuration is available through dependency injection.
public class HomeController : Controller
{
private readonly ConnectionString _connectionString;
public HomeController(IOptionsSnapshot<ConnectionString> connectionString)
{
_connectionString = connectionString.Value;
}
}
IOptionsSnapshot can reload configuration data when it changes.
More info about Configuration.
You must to load the appsettings.json file in Program.cs
public static void Main(string[] args)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false)
.Build();
string conStr = configuration.GetConnectionString("DefaultConnection");
// your code
var host = WebHost.CreateDefaultBuilder();
host.Run();
}
Make sure that you have installed following NuGet Packages before connect to the SQL Server Db.
First you have to create connection strings in appsettings.json like below
"ConnectionStrings": {
"DefaultConnection": "Server=DESKTOP-O57GSNN\\MSSQLSERVERNEW,Database=BookListRazor;Trusted_Connection=True;MultipleActiveResultSets=True"},
Here, DefaultConnection is connection Name you are going to use.Please provide any meaningful name you want and it will be use when configuring.Also provide Server Name from SQL Server Management Studio(see the picture below) and Provide a name for your database (Will be created).
Then create ApplicationDbContext.cs file and type below code
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
public DbSet<Book> Book { get; set; }
}
Book is the Model in my App.
Finally configure it with SQL Server configuration inside ConfigureServices method in Startup.cs file.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(option => option.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddRazorPages().AddRazorRuntimeCompilation();
}
DefaultConnection is connection Name which was already mentioned in appsettings.json

"Parameter count mismatch" error during adding Entity Framework migration under .NET Core 2.0

After migration of my project to .NET Core 2.0, fresh install of Visual Studio 15.5 and .NET CORE sdk 2.1.2, I am having an error when trying to add a migration using EF Core.
C:\Projects\SQLwallet\SQLwallet>dotnet ef migrations add IdentityServer.
An error occurred while calling method 'BuildWebHost' on class 'Program'.
Continuing without the application service provider. Error: Parameter count mismatch.
Done. To undo this action, use 'ef migrations remove'
As a result an empty migration class is created, with empty Up() and Down() methods.
The program.cs looks like:
public class Program
{
public static IWebHost BuildWebHost(string[] args, string environmentName)
{...}
public static void Main(string[] args)
{
IWebHost host;
host = BuildWebHost(args, "Development");
Please advise. The migration worked fine while on Core 1.0. I have a IDesignTimeDbContextFactory implemented, and my DBContext class has a parameterless constructor, so it could not be the reason.
My solution is to pass Array to HasData function, not generic List.
If you use List, try to convert array with ToArray function.
Here is an example:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var users = new List<User>();
var user1 = new User() { Id = 1, Username = "user_1" };
var user2 = new User() { Id = 2, Username = "user_2" };
users = new List<User>() { user1, user2 };
modelBuilder.Entity<User>().HasData(users.ToArray());
}
Take a look at the OnModelCreating method in your DbContext. Make sure it calls base.OnModelCreating(builder). It should look something like:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<T>().HasData(...);
}
If you are using builder.Entity<T>().HasData(...) to seed your data, make sure you pass it a T[] and not an IEnumerable.

How to add db context not in ConfigureServices method ASP.NET Core

Is there any posibility to add a db context in external class/method "on fly?" When I run the application, there is no any connection string, so I need to generate a db after typing some information(server, dbname, ect)
One way is to use the factory pattern, i.e. creating a service that will be used to create new instances of your context.
Here is an example, it is not a final solution and you will need to adapt it to your needs but it should give you an idea of the technique:
public interface IDbContextFactory
{
DbContext CreateDbContext(string connectionString);
}
public class DbContextFactory : IDbContextFactory
{
public DbContext CreateDbContext(string connectionString)
{
return new DbContext(connectionString);
}
}
Then in asp.net core, you can register the context factory and inject it in your controller:
services.AddSingleton<IDbContextFactory, DbContextFactory>();
public class SomeController
{
private IDbContextFactory contextFactory;
public SomeController(IDbContextFactory contextFactory)
{
this.contextFactory = contextFactory;
}
public IActionResult Index()
{
using(var db = contextFactory.CreateDbContext("Your connection string")) {
//Get some data
}
return View();
}
}
Instead of creating a DbContext you could combine the factory pattern with the unit of work and / or repository patterns to better separate concerns and to make sure you always dispose the context, etc...
Use new YourContext(new DbContextOptionsBuilder<YourContext>().Use...().Options)

How to pass IOptions through Dependency injection to another Project

I have a WebApplication targetting .net core.
I have also created a Class Library targetting .net core as well.
I am creating a Users Repository following this Dapper tutorial Here
It would be nice to be able to provide the option that was injected in start up of the WebApplication into the project that will be the data access layer.
Here is the code for the Users Repository in a separate project.
class UsersRepository
{
private readonly MyOptions _options;
private string connectionString;
public UsersRepository(IOptions iopt/// insert Option here )
{
_options = iopt.Value;
connectionString = _options.connString;
}
public IDbConnection Connection
{
get
{
return new SqlConnection(connectionString);
}
}
The WebApplication Project Startup looks as follows.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<MyOptions>(Configuration);
services.AddMvc();
}
and of course MyOptions is a class in the web application that has only one property connString
One possible design is to make a new interface for your repository configuration inside your class library, and have your MyOptions type implement that interface.
For example, in your class library you can do the following:
public interface IRepositoryConfig
{
string ConnectionString { get; }
}
public class UserRepository
{
public UserRepository(IRepositoryConfig config)
{
// setup
}
}
And in your WebAPI Startup class you can wire this up as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<MyOptions>(Configuration);
services.AddMvc();
services.AddScoped<IRepositoryConfig>(s =>
s.GetService<IOptions<MyOptions>>().Value
);
services.AddScoped<UserRepository>();
}
Doing this will allow you to use the Asp.Net Core configuration/options framework without having to reference any Asp.Net DLLs in your class library directly.