Easily switching between connection strings in .NET Core - sql-server

I've got a code base that uses EF Core and Dapper to perform actions on a database.
I want to set up a new copy of the site to develop some features and I want this site to connect to a new isolated copy of the database (dbConnectionDEBUG).
At the moment, I use the following setup:
startup.cs
...
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("dbConnectionMain")));
services.Configure<ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));
...
I have a ConnectionStrings class which is being populated correctly via the DI in startup:
public class ConnectionStrings
{
public string dbConnectionMain { get; set; }
public string dbConnectionDEBUG { get; set; }
public ConnectionStrings()
{
this.dbConnectionMain = "";
this.dbConnectionDEBUG = "";
}
}
Then, throughout my controllers/services I have access to ConnectionStrings and 99% of the time I'm doing the following to make DB calls:
using (var conn = new SqlConnection(_connectionStrings.dbConnectionMain))
{
conn.Open();
...
This would amount to a lot of code changes if I were to want to switch over to the 'DEBUG' db.
How do I easily switch between the connection strings in my code depending on what version of the system I'm working on.
If I could somehow do this dynamically that'd be great. The obvious determining factor would be the URL the site is operating on.
Alternatively, (as a single change) do I just manually change the connection string at the source (e.g keystore/appsettings). I'm not keen on this as it leaves room for human error.
Update (2)
Based on what #Nkosi mentioned I am pursuing this path:
Have one connection string 'Id' (i.e. dbConnection) used throughout
Differentiate the connection string value within this based on the environment the app is running/deployed in
I have another question:
If I have the following...
"MYAPPNAME": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:12345/;https://myapptestdomain.com/"
}
and:
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
IHostingEnvironment env = context.HostingEnvironment;
config.AddJsonFile($"appsettings.{env.EnvironmentName.ToLower()}.json", optional: true);
})
.UseStartup<Startup>();
...will this automatically pick up my site is in the Development mode based on the applicationUrl values OR will I have to manually add ASPNETCORE_ENVIRONMENT with a value Development on the server I deploy the app to?
Additional: My app is running in an Azure App Service.
Update (3) - Mission Complete
Just to finalise this question (in case anyone needs to know this), I have the following setup based on recommendations made by #Nkosi.
Connection String - I have one connection string Id/name dbConnection which is used in all appSettings (see below)
App Settings
I have a default appSettings.json with dbConnection that looks at the live database
I have an additional appSettings.Playground.json file with dbConnection that looks at my testing database
Azure - App Service - On my playground development slot I have added an App Setting for ASPNETCORE_ENVIRONMENT with the value 'Playground'
In my Program.cs file I have:
config.AddJsonFile($"appsettings.json", optional: true,reloadOnChange: true);
and
config.AddJsonFile($"appsettings.{env.EnvironmentName.ToLower()}.json", optional: true,reloadOnChange: true);
Just to note, I do also initialise a Vault on Azure which stores all my Keys and Secrets for the Azure based apps. Locally User Secrets is used.

ASP.NET Core reads the environment variable ASPNETCORE_ENVIRONMENT at app startup and stores the value in IHostingEnvironment.EnvironmentName.
Since the environment is being loaded, then it should be available from the hosting environment via the builder context
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) => {
string environment = context.HostingEnvironment.EnvironmentName; //get current environment
//load config based on environment.
config.AddJsonFile($"appsettings.{environment}.json", optional: true);
//...
})
//...
Reference Use multiple environments in ASP.NET Core

For simple apps and to keep the connection strings away from my repository I use preprocessor statements plus PATH/System Variables and for release I provide a connection string within the settings.json.
#define USE_FEATURE_X
using System;
namespace MyNamespace {
internal static class StaticConnectionStringFactory {
public static string GetConnectionString() {
#if DEBUG && !USE_FEATURE_X
var connectionString = Environment.GetEnvironmentVariable("CNNSTR_SQL_XYZ", EnvironmentVariableTarget.User);
#elif DEBUG && USE_FEATURE_X
var connectionString = Environment.GetEnvironmentVariable("CNNSTR_SQL_ABC", EnvironmentVariableTarget.User);
#else
var connectionString = Environment.GetEnvironmentVariable("SqlConnectionString", EnvironmentVariableTarget.Process);
#endif
return connectionString;
}
}
}

I think if you add 2 connection for debug and main then you will have face some difficulty because more member working in you team. may be some own wrongly use release mode for code development.
you can try this webconfig method:
public class ConnectionStrings
{
public string dbConnection { get; set; }
public ConnectionStrings()
{
bool Ismain = bool.Parse(System.Configuration.ConfigurationManager.AppSettings["HasLive"]);
if (Ismain)
{
dbConnection = "";// get Main connectionstring
}
else
{
dbConnection = "";// get Debug connectionstring
}
}
}
web.config:
<connectionStrings>
<add name="dbConnectionMain" connectionString="" providerName="System.Data.SqlClient" />
<add name="dbConnectionDEBUG" connectionString="" roviderName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
<add key="HasLive" value="false"/>
</appSettings>
</connectionStrings>

Related

Change SQL Server Connection String Dynamically inside an ASP.Net Core application

I open one database at the start, then need to open another database based on user selecting two values. The database selection has to be at run-time and will change every time.
Have tried to access the Connection String using the Connection String class and have tried other options like Singleton which I do not understand. I am running this on a local Windows 10 system running SQL Server Express. Am coding using Asp.Net Core 2.1
> ASP.Net Core v2.1
Building multi tenant, multi year application
Every client will have one SQL DATABASE per year
I hope to have a table with the following structure
COMPANY_CODE VARCHAR(3),
COMPANY_YEAR INT,
COMPANY_DBNAME VARCHAR(5)
Sample Data
COMPANY_CODE: AAD
COMPANY_YEAR: 19
COMPANY_DB: AAD19
COMPANY_CODE: AAD
COMPANY_YEAR: 18
COMPANY_DB: AAD18
COMPANY_CODE: AAD
COMPANY_YEAR: 17
COMPANY_DB: AAD17
So, every company will multiple rows - one for each financial year.
The COMPANY_DB column will store the DB name to open for that session.
Once the user is authenticated, I want to change the connection string to point to the database in the COMPANY_DB column of the selected row and then let the logged in user perform transactions.
I am unable to figure out how to change the connection string that is embedded in startup.cs.
Any tips on how to achieve this will be most appreciated.
I figured out that you are using one DbContext class for each database. See here for more information: docs.
Remove AddDbContext from Startup, remove OnConfiguring from DbContext and pass options to the constructor.
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
}
Then, write service providing DbContext:
public interface IBlogContextProvider
{
BlogContext GetBlogContext(string connectionString);
}
public class BlogContextProvider : IBlogContextProvider
{
BlogContext GetBlogContext(string connectionString)
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connectionString);
return new BlogContext(optionsBuilder);
}
}
Add service in your Startup.cs:
services.AddScoped<IBlogContextProvider, BlogContextProvider>();
Now you can use DI:
public class HomeController : Controller
{
private IBlogContextProvider _provider;
public HomeController(IBlogContextProvider provider)
{
_provider = provider;
}
public ActionResult Index()
{
using (var context = _provider.GetBlogContext(<your connection string>))
{
//your code here
}
return View();
}
}
EDIT: Of course, you can write ContextProvider as generic.

Can't connect to SQL 2008 database using .NET Core 2.0

UPDATE
I could never make this work with a "Windows Authentication" (domain) user. But with a "SQL Server Authentication" user everything is working like it's supposed to.
ORIGINAL QUESTION
My connectionString: Server=ip;Database=dbname;User Id=xxx\user;Password=pass;
The connection string is located in appsettings.json like this:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"ConnectionString": "Server=ip;Database=dbname;User Id=xxx\user;Password=pass;"
}
}
Then i pass it to a static class from the "Startup.cs" file, like this:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
Orm.DatabaseConnection.ConnectionString = Configuration["ConnectionStrings:ConnectionString"];
}
This is where I initiate the connection:
using System.Data.SqlClient;
namespace MyProject.Orm
{
public static class DatabaseConnection
{
public static string ConnectionString { get; set; }
public static SqlConnection ConnectionFactory()
{
return new SqlConnection(ConnectionString);
}
}
}
And this is my controller:
public string Get()
{
using (var databaseConnection = Orm.DatabaseConnection.ConnectionFactory())
{
var sections = databaseConnection.Query("SELECT * FROM myTable").ToList();
return sections.ToString();
}
}
Where this line:
var databaseConnection = Orm.DatabaseConnection.ConnectionFactory();
returns:
ServerVersion: "'databaseConnection.ServerVersion' threw an exception of type 'System.InvalidOperationException'"
Message: "Invalid operation. The connection is closed."
Source: "System.Data.SqlClient"
StackTrace: "at
System.Data.SqlClient.SqlConnection.GetOpenTdsConnection()\n
at
System.Data.SqlClient.SqlConnection.get_ServerVersion()"
And i get this error on new SqlConnection: "error CS0119: 'SqlConnection' is a type, which is not valid in the given context".
But the program execution doesn't stop because of these errors.
The application then hangs on the following line:
var sections = databaseConnection.Query("SELECT * FROM myTable").ToList();
I'm using Dapper as my ORM (not EntityFramework). In "myTable" sql table are only 17 rows and 5 columns so it should load fast.
I tried all kinds of different connectionStrings but it always fails. If i try the same with .NET Framework 4.5, everything works fine. The problem is .NET Core 2.0.
Any idea about fixing it is welcome. Because i spent too many hours on this already.
Try to add databaseConnection.Open().
public string Get()
{
using (var databaseConnection = new SqlConnection(#"Server=ip;Database=dbname;User Id=xxx\user;Password=pass;Pooling=false;"))
{
databaseConnection.Open();
var sections = databaseConnection.Query("SELECT * FROM myTable").ToList();
return sections.ToString();
}
}
To avoid problems with connection pool that described in comments you add Pooling=false; to connection string:
Server=ip;Database=dbname;User Id=xxx\user;Password=pass;Pooling=false;
Edit:
I hardcoded connection string and removed factory to make example smaller
Try creating a self-contained deployment, this should eliminate and strange dependency stuff. If it works then at least you know that it's due to some assembly binding type stuff.
The exception "error CS0119: 'SqlConnection' is a type, which is not valid in the given context" smells like it is.

net core 1 (dnx 4.5.1) with enterpriselibrary 6 - setting up the connection string

i ve big problems running enterprise library data access block with net core 1 (dnx 4.5.1)
How can i setup the default connection string for entlib
my appsettings.json
"ConnectionString": "Server=localhost\sqlexpress;Initial Catalog=blind;User Id=blind;Password=blind"
Here is my problem (no default connectionstring)
Database db = DatabaseFactory.CreateDatabase();
how can i pass the appsettings ConnectionString to the entlib databasefactory
any help would be greatly appreciated
I know it's an old question, but I have a similar setup (but using .NET Core 2.0) and it took me awhile to figure out how to set the default database connection without using the web.config to manage it.
What I did was include the default database and all of the connection strings in the appsettings.json and then in my Startup class I read the appsettings.json into an object that I defined to store the default db name and the connection strings and configure the default + named database using DatabaseFactory.SetDatabase.
DatabaseFactory.SetDatabases() Definition
public class DataConfiguration
{
public string DefaultDatabase { get; set; }
public List<ConnectionStringSettings> ConnectionStrings { get; set; }
}
public class Startup
{
public Startup(IConfiguration configuration)
{
//Get the Database Connections from appsettings.json
DataConfig = configuration.Get<DataConfiguration>();
var defaultDb = DataConfig.ConnectionStrings?.Find(c => c.Name == DataConfig.DefaultDatabase);
DatabaseFactory.SetDatabases(() => new SqlDatabase(defaultDb.ConnectionString), GetDatabase);
Configuration = configuration;
}
public Database GetDatabase(string name)
{
var dbInfo = DataConfig.ConnectionStrings.Find(c => c.Name == name);
if (dbInfo.ProviderName == "System.Data.SqlClient")
{
return new SqlDatabase(dbInfo.ConnectionString);
}
return new MySqlDatabase(dbInfo.ConnectionString);
}
}
Whenever there is documentation, I always suggest reading it as it is usually good. This is one of those examples, check out the "Getting Started with ASP.NET 5 and Entity Framework 6". There are several things that you need to do to ensure that you are correctly configured.
Setup your connection string and DI.
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
}
Also, notice the path in the configuration, it seems to differ from yours.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped((_) =>
new ApplicationDbContext(
Configuration["Data:DefaultConnection:ConnectionString"]));
// Configure remaining services
}

Change the connection string of nopCommerce?

I am using nopCommerce and I need to remove the connection string in the settings.txt file and insert the web.config file. How can i do this?
The most straightforward way to move the connection string out of settings.txt and into the web.config is to modify the Nop.Core.Data.DataSettingsManager. Specifically the LoadSettings() and SaveSettings() methods. You can store the connection string wherever you'd like (ideally in web.config), as long as those two methods read and write the configuration.
A rough example of the DataSettingsManager updated to support storing the connection string in web.config can be found in this Gist: http://git.io/vUPcI Just copy the connection string from settings.txt to web.config and name the connection "DefaultConnection" or adapt the code accordingly.
Just do two steps
Replace two method LoadSettings and SaveSettings in \nopCommerce\Libraries\Nop.Core\Data\DataSettingsManager.cs. Code from link of #Stephen Kiningham
/// <summary>
/// Load settings
/// </summary>
/// <param name="filePath">File path; pass null to use default settings file path</param>
/// <returns></returns>
public virtual DataSettings LoadSettings(string filePath = null)
{
try
{
System.Configuration.Configuration webConfig = WebConfigurationManager.OpenWebConfiguration(HttpRuntime.AppDomainAppVirtualPath);
return new DataSettings
{
DataConnectionString = webConfig.ConnectionStrings.ConnectionStrings["DefaultConnection"].ConnectionString,
DataProvider = webConfig.ConnectionStrings.ConnectionStrings["DefaultConnection"].ProviderName
};
}
catch (NullReferenceException)
{
return new DataSettings();
}
}
/// <summary>
/// Save settings to a file
/// </summary>
/// <param name="settings"></param>
public virtual void SaveSettings(DataSettings settings)
{
if (null == settings) throw new ArgumentNullException("settings");
System.Configuration.Configuration webConfig = WebConfigurationManager.OpenWebConfiguration(HttpRuntime.AppDomainAppVirtualPath);
webConfig.ConnectionStrings.ConnectionStrings["DefaultConnection"].ConnectionString = settings.DataConnectionString;
webConfig.ConnectionStrings.ConnectionStrings["DefaultConnection"].ProviderName = settings.DataProvider;
webConfig.Save();
}
Add connection string to your web config web.config
<connectionStrings>
<add name="DefaultConnection"
connectionString=" Data Source=localhost;Initial Catalog=nopcommerce;Integrated Security=True;Persist Security Info=False"
providerName="sqlserver">
</add>
</connectionStrings>
[1] In .NET Core (3.1 | NopCommerce 4.3) I created various appsettings.json files (including appsettings.json, appsettings.Development.json, appsettings.Integration.json, appsettings.Staging.json) and logic (beyond this discuss) determines the the correct settings to be used in the proper environment etc.
[2] \nopCommerce\Libraries\Nop.Core\Data\DataSettingsManager.cs
I created the following method:
public static string GetConnectionString()
{
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
string appSettingsFileName = env switch
{
"Development" => "appsettings.Development.json",
"Production" => "appsettings.json",
"Staging" => "appsettings.Staging.json",
"Integration" => "appsettings.Integration.json",
_ => "appsettings.json",
};
IConfigurationRoot configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile(appSettingsFileName).Build();
string connectionString = configuration.GetConnectionString("DefaultConnection");
return connectionString;
}
(returns the proper connection string for the proper environment)
[3] In LoadSettings -- I didn't change anything to get down to the line
Singleton<DataSettings>.Instance = JsonConvert.DeserializeObject<DataSettings>(text);
... I just added a new line below to replace the .ConnectionString with the connectionstring determined from our new method:
Singleton<DataSettings>.Instance.ConnectionString = GetConnectionString();
important:
When I ran it there were three or four places where there was a switch (case > default:) that was looking for a provider etc -- But I just copied the settings from MsSql down to the default: and it worked fine. I know this is sloppy but I am never using MySql for this project and so far as I am concerned its a non-issue. Either way - I broke it to make it work (multiple Azure App environments are more important).
I suggest they should have built it the regular way and just provided us with a SQL script for deployment (over engineered a non-issue?) since we usually have to do that anyway for custom development (seems silly to me to hard code a data settings file in App_Data) - but I trust their logic.
Please add this to your web.config under Nop.Web project :
<connectionStrings>
<add name="MyConnectionString"
connectionString="Data Source=serverName;Initial Catalog=DBName;Persist Security Info=False;UserID=userName;Password=password"
</connectionStrings>
Best Regards.
In addition to adding the connection to the web.config, you have to specify the providerName="sqlserver".
Ex) ;Initial Catalog=;Integrated
Security=False;User ID=;Password=;Connect
Timeout=30;Encrypt=True"
providerName="sqlserver" />
This is because the EfDataProviderManager in Nop.Data has a check for the provider name, and will throw an exception if you put the normal
providerName="System.Data.SqlClient"

What can I do to generate the DB in EF Code First?

I am not planning to use EF Code First in an MVC Website. I am looking to Utilize it in a App that has a WPF Client.
Projects are setup as
ContactManager.Core // Contains all
Entities(dll)
ContactManager.Data // Contains
the DataContext and other data
related Services(dll)
ContactManager.Services // Business
components (dll)
ContactManager.Client // WPF
Application
I am unable to generate a SQLExpress or SQLCE 4.0 DB. I am more interested in compact version db. I am not getting any error except my unit tests fail because it tries to connect a db that doesnt exist.
I found out the answer 2 Options:
Option 1:
In your DbContext you specify the connection strings in the base constructor:
public class RecetteContext : DbContext
{
public RecetteContext()
:base("<YourConnectionString HERE>")
{
}
public DbSet<Categorie> Categories { get; set; }
public DbSet<Recette> Recettes { get; set; }
}
}
Option 2:
The one I used, you give you connection string a name in the DbContext base constructor:
public RecetteContext()
: base("RecettesDatabase")
{ }
And in your App.Config file you add a ConnectionString with the same name:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="RecettesDatabase"
connectionString="Data Source=RecettesDB.sdf"
providerName="System.Data.SqlServerCe.4.0"/>
</connectionStrings>
</configuration>
Hope it solved your issue!

Resources