Multiple dbContexts in ASP.NET vNext and EF7 - c#

I'm trying to get along with building web systems with ASP.NET vNext using MVC 6 and EF7. I'm looking at this tutorial: http://stephenwalther.com/archive/2015/01/17/asp-net-5-and-angularjs-part-4-using-entity-framework-7
On the page you'll see how to add a dbContext to a project and it's registered in the startup file like this:
// Register Entity Framework
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<MoviesAppContext>();
And the context class looks like this:
public class MoviesAppContext:DbContext
{
public DbSet<Movie> Movies { get; set; }
}
It all works good, but now I'm in need of adding an additional DbContext. Though I don't know how to register this additional context so that it will be used by EF and possible to use in my project.
Let's say I've created a new context like this:
public class MyNewSuper:DbContext
{
public DbSet<Model1> Model1 { get; set; }
public DbSet<Model2> Model2 { get; set; }
}
How do I go ahead to register it for use in my project then?

Important Note: The syntax for configuring the Entity Framework 7 services has changed since this post, which was accurate as of the last few beta rounds. The same idea should still apply to the new syntax though.
Here is what I've been doing:
services.AddEntityFramework().AddSqlServer()
.AddDbContext<DataContextA>(options => options.UseSqlServer(Configuration.Get("StorageSettings:SQLConnectionString")))
.AddDbContext<DataContextB>(options => options.UseSqlServer(Configuration.Get("StorageSettings:SQLConnectionString")));
where StorageSettings:SQLConnectionString is a connection string for a SQL Express database. Currently, I have both DataContextA and DataContextB sharing the same database, but you can keep them separate. If you want to keep using the Configuration method (which I wasn't aware of, pretty cool!) you could do something like this:
{
"Data": {
"DefaultConnectionA": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=ContextADatabase;Trusted_Connection=True;MultipleActiveResultSets=true",
"DefaultConnectionB": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=ContextBDatabase;Trusted_Connection=True;MultipleActiveResultSets=true"
}
},
"EntityFramework": {
"DataContextA": {
"ConnectionStringKey": "Data:DefaultConnectionA:ConnectionString"
}
"DataContextB": {
"ConnectionStringKey": "Data:DefaultConnectionB:ConnectionString"
}
}
}
with
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<DataContextA>()
.AddDbContext<DataContextB>();
Both DataContextA and DataContextB can be injected into your controller:
public class MyController: Controller {
public MyController(DataContextA dataA, DataContextB dataB) {
// Do stuff
}
}

First of all, in something like config.json you can add yur connection strings. Something like the following will work
"Data": {
"BlogData": { "ConnectionString": "Server=tcp:YourHostname.net,1433;Database=YourDatabaseName;User ID=YourDBUser#YourDomain;Password=YourPassword;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" },
"Identity": { "ConnectionString": "Server=tcp:YourHostname.net,1433;Database=YourDatabaseName;User ID=YourDBUser#YourDomain;Password=YourPassword;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" }
},
You then have two DBContexts. Let's say:
YourApp.AppDBContext and YourApp.AppIdentityDBContext
You need to include these at the top of your CS file of course.
using YourApp.AppDBContext;
using YourApp.AppIdentityDBContext;
In startup.cs for example, in the startup method, your configuration builder will look like this:
var builder = new ConfigurationBuilder()
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
In the ConfigureServices method you will add your DBContexts as follows:
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration["Data:BlogData:ConnectionString"]))
.AddDbContext<AppIdentityDbContext>(options =>
options.UseSqlServer(Configuration["Data:Identity:ConnectionString"]));
I hope this helps. Feel free to give me a shout if I can expand on this further.

Related

Entity Framework Core 2.1 - Multiple Providers

What is the right way to work with multiple providers?
My Example:
appsettings.json
{
"ConnectionStrings": {
"Sqlite": "Data Source=database.db"
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("Sqlite")));
}
DatabaseContext.cs
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
public DbSet<TestModel> TestModel{ get; set; }
}
A easy way for multiple providers?
A solution with only one Context (Example for SQLite + MySQL + MSSQL + PostgreSQL (or others)):
appsettings.json
{
// Add Provider and ConnectionStrings for your EFC drivers
// Providers: SQLite, MySQL, MSSQL, PostgreSQL, or other provider...
"Provider": "SQLite",
"ConnectionStrings": {
"SQLite": "Data Source=mydatabase.db",
"MySQL": "server=localhost;port=3306;database=mydatabase;user=root;password=root",
"MSSQL": "Server=(localdb)\\mssqllocaldb;Database=mydatabase;Trusted_Connection=True;MultipleActiveResultSets=true",
"PostgreSQL": "Host=localhost;Database=mydatabase;Username=root;Password=root"
}
}
Single DatabaseContext.cs
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
// add Models...
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Check Provider and get ConnectionString
if (Configuration["Provider"] == "SQLite")
{
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("SQLite")));
}
else if (Configuration["Provider"] == "MySQL")
{
services.AddDbContext<DatabaseContext>(options =>
options.UseMySql(Configuration.GetConnectionString("MySQL")));
}
else if (Configuration["Provider"] == "MSSQL")
{
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MSSQL")));
}
else if (Configuration["Provider"] == "PostgreSQL")
{
services.AddDbContext<DatabaseContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL")));
}
// Exception
else
{ throw new ArgumentException("Not a valid database type"); }
}
Now we can do a singel migration
Add-Migration InitialCreate
Only edit every output of Add-Migration and add driver specific attributes:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Mytable",
columns: table => new
{
Id = table.Column<int>(nullable: false)
// Add for SQLite
.Annotation("Sqlite:Autoincrement", true)
// Add for MySQL
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn)
// Add for MSSQL
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn)
// Add for PostgreSQL
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
// Or other provider...
Name = table.Column<string>(maxLength: 50, nullable: false),
Text = table.Column<string>(maxLength: 100, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Mytable", x => x.Id);
});
}
EDIT:
or you use string ID "DatabaseGenerated"
so you would not have to edit migrationBuilder and the add migration is multiple providers capable without ".Annotation"
EXAMPLE Model:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WebApplication.Models
{
public class Mytable
{
// This generate a String ID
// No ID modification needed for providers
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
// ....
}
}
Now ready for Update-Database
You may want to consider a utility like AdaptiveClient. AdaptiveClient allows you to create a single DbContext with multiple provider-specific implementations of your services (MSSQL, MySQL, SQLite, etc). AdaptiveClient injects the correct implementation based on the connection string in use.
AdaptiveClient also allows you to inject transport-specific service implementations. For example many applications run both locally (same LAN as database server) and remotely (use WCF or REST). When running locally AdaptiveClient will inject an implementation of your service that talks directly to your database. This gives a ~10x performance improvement. When running remotely AdaptiveClient injects a WCF or REST implementation.
See also:
AdaptiveClient.EntityFrameworkCore
Demo Application
AdaptiveClient is available as a nuget package.
Disclaimer: I am the author of AdaptiveClient.
Seimann's answer is good but I found working with migrations to be a pain. I wanted little or no manual work to get it working. I found the easiest way was to create a separate assembly for each provider and add an implementation of IDesignTimeDbContextFactory.
Another solution is to create a design time assembly but selecting which provider to use for migrations turned out to be difficult, at least until this feature is implemented here. I tried the suggested method of setting an environment variable before executing the migrations but I found using compiler constants to select the correct provider to be easier.
I organized this by creating a shared project to be used by all providers. Here is an example implementation that pulls your main projects configuration settings. This class will support both methods explained above so it can be simplified depending on your needs.
#if DEBUG
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
namespace Database.DesignTime
{
public class ApplicationDbContextDesignTimeFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Path.GetFullPath(#"..\MainProjectDirectory"))
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json")
.Build();
// Determine provider from environment variable or use compiler constants below
var databaseProvider = Environment.GetEnvironmentVariable("DatabaseProvider");
#if SQLSERVER
databaseProvider = "SqlServer";
#endif
#if POSTGRESQL
databaseProvider = "PostgreSql";
#endif
var connectionString = configuration.GetConnectionString($"{databaseProvider}Connection");
var contextBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
switch (databaseProvider)
{
#if SQLSERVER
case "SqlServer":
contextBuilder.UseSqlServer(connectionString, dbOptions =>
{
dbOptions.MigrationsAssembly("Database.SqlServer");
});
break;
#endif
#if POSTGRESQL
case "PostgreSql":
contextBuilder.UseNpgsql(connectionString, dbOptions =>
{
dbOptions.MigrationsAssembly("Database.PostgreSql");
});
break;
#endif
default:
throw new NotSupportedException(databaseProvider);
}
return new ApplicationDbContext(contextBuilder.Options);
}
}
}
#endif
Then in your database migration project add the compiler constant for each provider. For example:
Database.SqlServer.csproj
<DefineConstants>SQLSERVER</DefineConstants>
Database.PostgreSql.csproj
<DefineConstants>POSTGRESQL</DefineConstants>
When you want to add migrations from within VS, open the Package Manager Console and select the migration project as the Default project. When executing the command, you need to specify the project containing the implementation of IDesignTimeDbContextFactory you want to use.
Add-Migration Initial -StartupProject "Database.SqlServer"
Now you can switch back to your main project and use it as normal. Just for reference this is my relevant appsettings.json and startup code.
{
"DatabaseProvider": "SqlServer",
"ConnectionStrings": {
"SqlServerConnection": "Server=(localdb)\\mssqllocaldb;Database=DatabaseName;Trusted_Connection=True;MultipleActiveResultSets=true",
"PostgreSqlConnection": "Host=host;Database=DatabaseName;User ID=Test;Password=secrectPass"
}
services.AddDbContext<ApplicationDbContext>(options =>
{
switch (Configuration["DatabaseProvider"])
{
case "SqlServer":
options.UseSqlServer(Configuration.GetConnectionString("SqlServerConnection"), dbOptions =>
{
dbOptions.MigrationsAssembly("Database.SqlServer");
});
break;
case "PostgreSql":
options.UseNpgsql(Configuration.GetConnectionString("PostgreSqlConnection"), dbOptions =>
{
dbOptions.MigrationsAssembly("Database.PostgreSql");
});
break;
}
});
There is another suggested way to accomplish this as explained here but I found creating derived classes means the migration will only work for instances of the derived class and not the base class. So you would need to specify the derived class type in AddDbContext. The other method mentioned requires manual work which I want to avoid.

How to inject connection string into external assembly (project) controller?

My Web API is using other project for one controller. Service works fine. But I am struggling to inject connection string from main Web API project into controller in external project.
How could this be achieved?
public class MyExternalController : Controller
{
private string _connStr;
public MyExternalController(string connStr)
{
_connStr = connStr;
}
// actions here
}
As others said in the comments, for something like a controller, you should be injecting something concrete like a DbContext, not a connection string. However, for future reference your issue here is injecting a string. There's no way to register something in the DI container to satisfy a dependency like that. Instead, you should inject your configuration or a strongly-typed configuration class.
Injecting IConfigurationRoot is a bit of an anti-pattern, but for something like a connection string, it's fine:
public MyExternalController(IConfigurationRoot config)
{
_connStr = config.GetConnectionString("MyConnectionString");
}
For everything else, though, you should use strongly-typed configuration classes.
public class FooConfig
{
public string Bar { get; set; }
}
Then, in ConfigureServices:
services.Configure<FooConfig>(Configuration.GetSection("Foo"));
Which of course would correspond with some bit of config like:
{
"Foo": {
"Bar": "Baz"
}
}
Then, in your controller, for example:
public MyExternalController(IOptionsSnapshot<FooConfig> fooConfig)
{
_fooConfig = fooConfig.Value;
}

ASP.NET Core Configuration Section in Startup

I am migrating a ASP.NET 5 RC1 project to ASP.NET Core, and have come across an interesting issue I've not yet seen, or found a solution for.
In order to use configuration settings within Startup I have previously retrived the configuration the following way
// Works fine for DI both in ASP.NET 5 RC1 and ASP.NET Core
services.Configure<SomeConfigurationClass>(Configuration.GetSection("SomeConfigurationSection"));
// How I previous retrieved the configuration for use in startup.
// No longer available in ASP.NET Core
var someConfigurationToUseLater = Configuration.Get<SomeConfigurationClass>("SomeConfigurationSection");
After updating to ASP.NET Core 1.0 it seems Configuration.Get<T>() is no longer available.
I have tried updating the code to use Configuration.GetValue<T>() however this does not seem to work with objects and will only work when providing a path to a value. This has left me with a workaround for most of my configuration classes like so
var someConfigurationName = "someConfiguration";
var someConfigurationClass = new SomeConfigurationClass()
{
Value1 = Configuration.GetValue<string>($"{someConfigurationName}:value1"),
Foo = Configuration.GetValue<string>($"{someConfigurationName}:foo"),
Bar = Configuration.GetValue<string>($"{someConfigurationName}:bar")
};
However this is an issue when the configuration class contains an array of objects. In my case an array of Client objects
public class ClientConfiguration
{
public Client[] Clients { get; set; }
}
With the following configuration
"configuredClients": {
"clients": [
{
"clientName": "Client1",
"clientId": "Client1"
},
{
"clientName": "Client2",
"clientId": "Client2"
}
]
}
Where this would previously bind to the Clients property of my configuration class no problem, I can no longer find a way of doing so in ASP.NET Core 1.0
Updated Answer
For ASP Core 1.1.0 generic model binding is now done using Get:
var config = Configuration.GetSection("configuredClients").Get<ClientConfiguration>();
Original Answer
How about this:
var config = Configuration.GetSection("configuredClients").Bind<ClientConfiguration>();
With ASP.NET Core 2.0 (basically Core 1.1+), the IConfiguration is injected to Startup, and that can be used within ConfigureServices() and Configure() methods.
As shown in the accepted answer, the configuration can be bound to an object. But if just one value is required, the key based approach works well.
The IConfiguration still works with colon : separated string keys. And for array, use 0-based index. Or use the the generic getValue<T>() method with same keys. See example below:
var clientId2 = Configuration["configuredClients:clients:1:clientId"]?.ToString();
var clientName1 = Configuration.GetValue<string>("configuredClients:clients:0:clientName");
To use the same configuration values in other classes (e.g. Controllers)
Either inject the IConfiguration and use the same key-based approach like above. Or
Register an instance of the strongly-typed configuration object with the DI container, and inject that object directly into client classes.
Sample code below:
//In Startup.ConfigureServices()
var clientConfig = Configuration.GetSection("configuredClients")
.Get<ClientConfiguration>();
services.AddSingleton(clientConfig);
//Controller
public class TestController : Controller
{
IConfiguration _configStore;
ClientConfiguration _clientConfiguration;
public TestController(IConfiguration configuration,
ClientConfiguration clientConfiguration)
{
_configStore = configuration;
_clientConfiguration = clientConfiguration;
}
public IActionResult Get()
{
//with IConfiguration
var clientId1 = _configStore
.GetValue<string>("configuredClients:clients:0:clientId");
//with strongly typed ClientConfiguration
var clientName1 = _clientConfiguration.Clients[0]?.ClientName;
return new OkObjectResult("Configuration test");
}
}
More examples here.
You don't read the configuration manually generally in ASP.NET Core yourself, instead you create an object that matches your definition. You can read more on that in the official documentation here.
E.g.
public class MyOptions
{
public string Option1 { get; set; }
public int Option2 { get; set; }
}
public void ConfigureServices(IServiceCollection services)
{
// Setup options with DI
services.AddOptions();
services.Configure<MyOptions>(Configuration);
}
Then you just inject the options IOptions<MyOptions> where you need them.
If you want to get first "clientName"(expected "Client1"), just write:
Configuration.GetSection("configuredClients")["clients:0:clientName"];
Update for comment
Install .NET Core 1.0.1 and go with #TomMakin's way.

Application Settings in custom class ASP.Net 5 MVC 6

Playing around with ASP.Net 5 MVC. Seen this question jumping around but not an full answer. What I want to do is have a helper class that is able to access the AppSettings. I can access it in the controller and the view but haven't figured out how to access it on my own custom class. Have startup configured like so.
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOptions();
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
}
.................
.................
So in your config.json file, suppose you have following settings
{
"smtp": {
"SenderEmail": "a#b.com",
"SenderFrom": "Test User"
}
}
Then in your ConfigureServices method you need to do something like that
services.Configure<SmtpEmailSetting>(Configuration.GetSection("smtp"));
This is your SmtpEmailSetting looks like
public class SmtpEmailSetting
{
public string SenderEmail { get; set; }
public string SenderFrom { get; set; }
}
and this is how you access your settings in any service or controller
public class SendEmailService
{
private readonly SmtpEmailSetting _smtpEmailSetting;
public SendEmailService(IOptions<SmtpEmailSetting> smtpOptions )
{
_smtpEmailSetting = smtpOptions.Value;
}
public void SendEmail()
{
var fromEmail = _smtpEmailSetting.SenderEmail;
var displayName = _smtpEmailSetting.SenderFrom;
}
}
So basically you use your settings or options (whatever you prefer to call) should be used in constructor as a generic type parameter of IOptions<> class. Hope it helps
In order to access your AppSettings properties in your custom class, make configuration as a static instance such as:
public static IConfigurationRoot Configuration { get; set; }
and make use of your AppSettings any where in your application (for connectionstring example) as:
var connectionString = Startup.Configuration["Data:DefaultConnection:ConnectionString"];
Just to add to adeel41's answer, this is correct and works great, but for myself, I didn't want to drag around an IOption object when using dependency injection.
So I prefer to do something like
services.AddSingleton<ISmtpEmailSettings>(Configuration.GetSection("SmtpSettings").Get<SmtpEmailSettings>());
Most importantly is the Get syntax added to GetSection to deserialize your JSON to an object.
At the time of RC1, I took inspiration from this post by Rick Strahl, which worked great. That is very similar to other approaches already proposed.
This answer is just to update with my findings as of RTM release. It seems like Configuration.GetSection(string topLevelKey) does not work anymore, at least for me it always returns null (even if configuration sources are set correctly).
After some search, this other SO thread pointed me in the right direction, by using:
// create empty config object
var smtpEmailSetting = new SmtpEmailSetting();
// fill it from configuration section
Configuration.GetSection("smtp").Bind(smtpEmailSetting);
// use it, e.g. by registering it into DI
services.Configure<SmtpEmailSetting>(smtpEmailSetting);
HTH
If you need it in your own class, it's probably right to pass it into the constructor of that class, or as a parameter. Eg;
public class Notifications
{
public Notifications(AppSettings settings) {
this.settings = settings;
}
public void SendEmail(string subject, string body) {
SmptClient.Send(subject, body, settings["email address"]);
}
}
So typically, you'd pass it through from your controller.
This avoids a global variable, which is always a good thing, I think.

C# ASP.Net 5 configuration and backwards compatibility with Class Libraries

I've got multiple legacy libraries which configure themselves via the ConfigurationManager. Example:
var port = ConfigurationManager.AppSettings["service.port"];
The new ASP.Net system prefers strongly typed models based on an additional "config.json" file. Example (taken from Rick Strahl's Web Log):
//from AppSettings.cs
public class AppSettings
{
public string SiteTitle { get; set; }
}
//from config.json
{
"AppSettings": {
"SiteTitle": "WebApplication2",
},
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=blahfoo;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
}
// from Startup.cs
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
// Setup configuration sources.
var configuration = new Configuration()
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
configuration.AddEnvironmentVariables();
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
// Add Application settings to the services container.
services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
…
}
}
My question: Is there a way to embrace the new ASP.Net 5 and it's strongly typed configuration methodology while maintaining backwards compatibility with my other application libraries?
Or rather, can I utilize common libraries from our portfolio without having to rewrite them?
Your problem is that you relied on a concrete implementation of configuration and used the ConfigurationManager's static members from your classes instead of writing a SOLID implementation with proper dependency injection.
You could find some hacky tricks where you don't have to change your code to make use of the new configuration model, but I reckon you should do yourself a favour and use this as an opportunity to actually re-factor your code and abstract your current configurations behind one simple interface like e.g.:
public interface IMyAppConfiguration
{
string Setting1 { get; }
string Setting2 { get; }
SomeOtherMoreComplexSetting Setting3 { get; }
}
Then inject this dependency in every class where you require one of the settings and provide one implementation which wraps the current ConfigurationManager class and another implementation which wraps the new configuration model.
This is a perfect example why SOLID design is important and makes code maintenance and innovation easier when done right.

Categories

Resources