I have a simple WPF/EF Core 2.2.4 application that uses postgres.
I'm analyzing the possible strategies to migrate it on SQL Server.
In non ORM applications, it's quite common to limit the database-specific reference to the connection string togheter with providing a way to dynamically load the database driver (thinking of JDBC model). In that model you have to address the problem of writing SQL that works cross databases.
Here the problem of SQL writing, actually the major one, is solved right from the start. So I find it quite paradoxical that we sort of reintroduce database dependency in the form of helper methods.
My first question is about the DbContext. The OnConfiguring receives a DbContextOptionsBuilder that is used to pass Connection string.
But in order to pass the connection string you use a database-specific method that is provided as an extension method by the database provider.
That is the optionsBuilder.UseNpgsql(connstr) in the following example.
How should I address this in a database-independend application?
class MyDbContext: DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connstr =
ConfigurationManager
.ConnectionStrings["MYAPP_PROD"].ConnectionString;
optionsBuilder.UseNpgsql(connstr);
}
}
The second question is: how can I load the entire database package in a dynamic way, so that I can manage to configure it instead of harcoding it?
Actually I use NuGet to get the package:
Npgsql.EntityFrameworkCore.PostgreSQL
Say that I want to use:
Microsoft.EntityFrameworkCore.SqlServer
How can this be done?
Use the strategy pattern to register the relevant database provider based on external configuration.
interface IDbProvider {
bool AppliesTo(string providerName);
DbContextOptions<T> LoadProvider<T>();
}
public class PostgresSqlProvider : IDbProvider {
public bool AppliesTo(string providerName) {
return providerName.Equals("Postgres");
}
public DbContextOptions<T> LoadProvider<T>() {
//load provider.
}
}
var providers = new [] {
new PostgresSqlProvider()
};
var selectedDbProvider = ""; //Load from user input / config
var selectedProvider = providers.SingleOrDefault(x => x.AppliesTo(selectedDbProvider));
if(selectedProvider == null) {
throw new NotSupportedException($"Database provider {selectedDbProvider} is not supported.");
}
var options = selectedProvider.LoadProvider<DbContext>();
This scenario is already covered by EF Core. Configuring providers should be done in Startup.ConfigureServices, using any of the AddDbContext methods that accept a builder action.
In the simplest case (dirtiest?), you can select providers based on a flag or a value that comes from the config system itself eg :
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
var connString=Configuration.GetConnectionString("SchoolContext");
var useSqlServer=Configuration.GetSection("MyDbConfig").GetValue<bool>("UseSqlServer");
services.AddDbContext<SchoolContext>(options =>{
if (useSqlServer)
{
options.UseSqlServer(connString);
}
else
{
options.UseNpgsql(connString);
}
});
}
or
var provider=Configuration.GetSection("MyDbConfig").GetValue<ProviderEnum>("Provider");
services.AddDbContext<SchoolContext>(options =>{
switch (provider)
{
case ProviderEnum.SqlServer:
options.UseSqlServer(connString);
break;
case ProviderEnum.Postgres :
options.UseNpgsql(connString);
break;
...
}
});
That flag can come from configuration as well, eg from the command-line, environment variables, etc.
Refactoring to .... lots
Extension method
This code can be extracted to an extension method on IServiceCollection, similar to other contexts, eg:
public static ConfigureContexts(this IServiceCollection services,string connString, string provider)
{
services.AddDbContext<SchoolContext>(options =>{
switch (provider)
{
case ProviderEnum.SqlServer:
options.UseSqlServer(connString);
break;
case ProviderEnum.Postgres :
options.UseNpgsql(connString);
break;
...
}
});
}
and used :
var connString=Configuration.GetConnectionString("SchoolContext");
var provider=Configuration.GetSection("MyDbConfig").GetValue<ProviderEnum>("Provider");
services.ConfigureContexts(provider,connString);
Builder picker
The builder, configuration patterns allow many variations that can handle complex scenarios. For example, we can pick a builder method in advance :
var efBuilder= SelectBuilder(provider,connString);
services.AddDbContext<SchoolContext>(efBuilder);
...
Action<DbContextOptionsBuilder> SelectBuilder(ProviderEnum provider,string connString)
{
switch (provider)
{
case ProviderEnum.SqlServer:
return ConfigureSql;
case ProviderEnum.Postgres :
return ConfigurePostgres;
}
void ConfigureSqlServer(DbContextOptionsBuilder options)
{
options.UseSqlServer(connString);
}
void ConfigurePostgres(DbContextOptionsBuilder options)
{
options.UseNpgSql(connString);
}
}
In C# 8 this could be reduced to:
Action<DbContextOptionsBuilder> SelectBuilder(ProviderEnum provider,string connString)
{
return provider switch (provider) {
ProviderEnum.SqlServer => ConfigureSql,
ProviderEnum.Postgres => ConfigurePostgres
};
void ConfigureSqlServer(DbContextOptionsBuilder options)
{
options.UseSqlServer(connString);
}
void ConfigurePostgres(DbContextOptionsBuilder options)
{
options.UseNpgSql(connString);
}
}
Concrete config class
Another possibility is to create a strongly-typed configuration class and have it provide the builder :
class MyDbConfig
{
public ProviderEnum Provider {get;set;}
....
public Action<DbContextOptionsBuilder> SelectBuilder(string connString)
{
return provider switch (provider) {
ProviderEnum.SqlServer => ConfigureSql,
ProviderEnum.Postgres => ConfigurePostgres
};
void ConfigureSqlServer(DbContextOptionsBuilder options)
{
options.UseSqlServer(connString);
}
void ConfigurePostgres(DbContextOptionsBuilder options)
{
options.UseNpgSql(connString);
}
}
}
and use it :
var dbConfig=Configuration.Get<MyDbConfig>("MyDbConfig");
var efBuilder=dbCongig.SelectBuilder(connString);
services.AddDbContext<SchoolContext>(efBuilder);
Related
EDIT: I also have a completely different approach where I have just about all of that crap commented out. I am able to call into my method and I configured the connect string to connect to the server, but not to the database. Then I want to connect to the different database depending on some data passed in (like the database name maybe?). Is there a way to call OnConfiguring on the fly so I can configure my connection string to be different each time I call my method?
I know some of you are going to look at this and roll your eyes with my stupidity, but we all had to start somewhere! I have a scenario with one database server but multiple databases all which share the same schema. Currently I am rewriting code for each database and it is a mess, so I am trying to clear it up. I have gotten myself pretty confused here so I am looking for advice from some of you gurus out there. I am a beginner here and trying to do something I find very advanced here so go easy on me. I will keep my examples with just two databases, but really there are 10+ databases that are all the same that I need to switch between often. My goal is to try to get rid of this 1, 2, 3, 4, 9, 10, etc stuff all over the place when I want to save or access a record, and have one thing controlling it all.
I created a base class for my database context
public partial class opkDataBaseContext : DbContext
{
private DbContextOptions<opkData1Context> options1;
private DbContextOptions<opkData2Context> options2;
public opkDataBaseContext()
{
}
public opkDataBaseContext(DbContextOptions<opkDataBaseContext> options)
: base(options)
{
}
public opkDataBaseContext(DbContextOptions<opkData1Context> options)
{
this.options1 = options;
}
public opkDataBaseContext(DbContextOptions<opkData2Context> options)
{
this.options2 = options;
}
public virtual DbSet<OrgUnits> OrgUnits { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
...
}
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
public void CreateNewOrganization(CreateCompleteOrgViewModel model)
{
var nameParameter = new SqlParameter("#TopLevelOrgName", model.Name);
var codeParameter = new SqlParameter("#Code", model.Code);
var DbNameParameter = new SqlParameter("#DBName", model.DbCatalog);
var debugParameter = new SqlParameter("#debug", "0");
var firstNameParameter = new SqlParameter("#FirstName", model.FirstName);
var lastNameParameter = new SqlParameter("#LastName", model.LastName);
var userNameParameter = new SqlParameter("#Username", model.UserName);
this.Database.ExecuteSqlRaw("CreateRootOrg #TopLevelOrgName, #Code, #DBName, #debug, #FirstName, #LastName, #Username",
nameParameter, codeParameter, DbNameParameter, debugParameter, firstNameParameter, lastNameParameter, userNameParameter);
}
Here is my Startup.cs
public void ConfigureServices(IServiceCollection services)
{
if (_env.IsProduction())
{
var opkCoreConnection = Configuration.GetConnectionString("opkCoreDatabase");
services.AddDbContext<opkCoreContext>(options => options.UseSqlServer(opkCoreConnection));
var opkData1Connection = Configuration.GetConnectionString("opkData1Database");
services.AddDbContext<opkData1Context>(options => options.UseSqlServer(opkData1Connection));
var opkData2Connection = Configuration.GetConnectionString("opkData2Database");
services.AddDbContext<opkData2Context>(options => options.UseSqlServer(opkData2Connection));
var opkDataLocalBaseConnection = Configuration.GetConnectionString("opkDataBaseDatabase");
services.AddDbContext<opkDataBaseContext>(options => options.UseSqlServer(opkDataLocalBaseConnection));
Then I have one of these for each database:
public partial class opkData1Context : opkDataBaseContext
{
public opkData1Context()
{
}
public opkData1Context(DbContextOptions<opkData1Context> options)
: base(options)
{
}
My current error is:
"Error while validating the service descriptor 'ServiceType: Models.DataModels.opkDataBaseContext Lifetime: Scoped ImplementationType: Models.DataModels.opkDataBaseContext': Unable to activate type 'Models.DataModels.opkDataBaseContext'. The following constructors are ambiguous:\r\nVoid .ctor(Microsoft.EntityFrameworkCore.DbContextOptions'1[Models.DataModels.opkDataBaseContext])\r\nVoid .ctor(Microsoft.EntityFrameworkCore.DbContextOptions'1[Models.opkData1Context])"} System.Exception {System.InvalidOperationException}
I have been messing with this all day. First, am I even going down the right path or is this just a dumb idea? Second, any idea where I am going wrong? Thank you!
I have a scenario with one database server but multiple databases all which share the same schema
This is very common, even a best-practice, in Software-as-a-Service (SaaS) applications.
While this is not obvious, it turns out to be quite simple. You just need some way to pick the connection string at runtime, probably based on a combination of the config and something in the HttpContext (like the user's identity, the path, the host header, etc). Then you configure the DbContext for DI like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddDbContext<MyDbContext>((sp, opt) =>
{
var httpContext = sp.GetRequiredService<IHttpContextAccessor>();
var config = sp.GetRequiredService<IConfiguration>();
string connectionString = GetConnectionStringFromHttpContext(httpContext, config);
opt.UseSqlServer(connectionString, o => o.UseRelationalNulls());
});
services.AddControllers();
}
where GetConnectionStringFromHttpContext is a custom method that builds the connection string based on the config and the HttpContext.
You could take a look a feature introduced in EF Core 5, which can change the connection or connection string on an already initialized context
I'm using Simple Injector as DI Container in a project.
The problem is that I have a SqliteStorage-class, which needs the path to the db. There are multiple dbs, so I need a way to inject the path to the SqliteStorage-class at creation.
My code looks as follows (simplified without interfaces):
public class SqliteStorageOptions
{
public string Path {get; set;}
}
public class SqliteStorage
{
private readonly string _path;
public SqliteStorage(SqliteStorageOptions options)
{
_path = options.Path;
}
}
public class Db1
{
private readonly SqliteStorage _sqlite;
public Db1(SqliteStorage sqlite)
{
_sqlite = sqlite;
}
}
public class Db2
{
private readonly SqliteStorage _sqlite;
public Db1(SqliteStorage sqlite)
{
_sqlite = sqlite;
}
}
// without di
var db1 = new Db1(new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" });
var db2 = new Db2(new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" });
Possible Solutions:
Include SqliteStorageOptions as parameter at every method in SqliteStorage.
Provide a init-method in SqliteStorage
Create a SqliteStorageFactory with a public SqliteStorage Create(SqliteStorageOptions options)-method.
So are there any built-in solution to my problem in simple-injector or can someone provide another (better) solution?
Thanks
Edit 1:
I added some code. Db1 and Db2 both connect to sqlite-dbs (different dbs, different schema), so I wanted to extract all the sqlite-stuff to its own class SqliteStorage. So, the SqliteStorage needs to know the db path.
Which solution is best depends a bit on whether you require Auto-Wiring (automatic constructor injection) or not. Using conditional registrations (using RegisterConditional) is a good pick, but you have be aware that it is limited to determining the injection based on only its direct parent. This means that you can't make SqliteStorageOptions conditional based on its parent parent (either Db1 or Db2).
If the Db1 and Db2 classes solely depend on a SqliteStorage and don't require any other dependencies, Auto-Wiring is not a real issue and your registrations can be as simple as the following:
container.Register<Db1>(
() => new Db1(new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" }));
container.Register<Db2>(
() => new Db2(new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" });
In case Auto-Wiring is required inside Db1 and Db2, RegisterConditional gives a good alternative, because it enables Auto-Wiring:
container.Register<Db1>();
container.Register<Db2>();
container.RegisterConditional<SqliteStorage>(
Lifestyle.CreateRegistration(
() => new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" }),
container),
c => c.Consumer.ImplementationType == typeof(Db1));
container.RegisterConditional<SqliteStorage>(
Lifestyle.CreateRegistration(
() => new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" }),
container),
c => c.Consumer.ImplementationType == typeof(Db2));
In this code snippet, both Db1 and Db2 are registered 'normally', while the SqliteStorage registrations are conditionally injected based on thei consumer.
This registration is more complex, because RegisterConditonal need to be supplied with a Registration instance: there is no RegisterConditional overload that directly accepts a Func<T> factory delegate.
You can have 2 singletons one per each database connection. Let's consider an example, firstly we'll need to create an interface for your StorageService:
public interface IStorage
{
void UsePath();
}
Now let's create couple of implementations of this storage service:
public class RedisStorage: IStorage
{
private readonly string _path;
public RedisStorage(string path)
{
_path = path;
}
public void UsePath()
{
Console.WriteLine($"Here's path: {_path}");
}
}
public class SqlStorage: IStorage
{
private readonly string _path;
public SqlStorage(string path)
{
_path = path;
}
public void UsePath()
{
Console.WriteLine($"Here's path: {_path}");
}
}
Enum to differentiate between implementations of IStorage:
public class StorageSource
{
public enum StorageTypes
{
Redis=1,
Sql=2
}
}
Once we are done with that, let's create a wrapper for a storage source:
public interface IStorageWrapper
{
void DoStuff();
}
Now comes a tricky part, instantiate a storage wrapper service decorator:
public class StorageServiceWrapper: IStorageWrapper
{
private readonly Func<string, IStorage> _storage;
public StorageServiceWrapper(Func<string, IStorage> storage)
{
_storage = storage;
}
public void UsePath()
{
_storage(StorageSource.StorageTypes.Redis.ToString()).DoStuff();
//uncomment for sql
//_storage(StorageSource.StorageTypes.Sql.ToString()).DoStuff();
}
}
To achieve this, you will need to register your classes in Startup.cs as follows:
services.AddScoped<IStorageWrapper, StorageServiceWrapper>();
services.AddSingleton<RedisStorage>();
services.AddSingleton<SqlStorage>();
services.AddTransient<Func<string, IStorage>>(serviceProvider => key =>
{
switch (key)
{
case "Redis":
return serviceProvider.GetService<RedisStorage>();
default:
return serviceProvider.GetService<SqlStorage>();
}
});
This wouldn't be as beautiful as calling _storage.DoStuff();, but I believe would help you with the solution of your problem. If you still want to keep it handy, consider managing your settings file and injecting proper IOptions<> instance with a conn string you need and registering a factory method.
Consider that I have configured EF with a .NET Core web app:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(...));
I can also download a package to support for example SQLite:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(...));
How can we allow a user to "select" the provider on app install? I mean - for example, in WordPress you can choose from a dropdown.
Is this possible in .NET Core? The only way I see is to restart the app only...
Here is an example on how you can implement a DbContextFactory or a DbContextProxy<T> which will create the correct provider and return it.
public interface IDbContextFactory
{
ApplicationContext Create();
}
public class DbContextFactory() : IDbContextFactory, IDisposable
{
private ApplicationContext context;
private bool disposing;
public DbContextFactory()
{
}
public ApplicationContext Create()
{
if(this.context==null)
{
// Get this value from some configuration
string providerType = ...;
// and the connection string for the database
string connectionString = ...;
var dbContextBuilder = new DbContextOptionsBuilder();
if(providerType == "MSSQL")
{
dbContextBuilder.UseSqlServer(connectionString);
}
else if(providerType == "Sqlite")
{
dbContextBuilder.UseSqlite(connectionString);
}
else
{
throw new InvalidOperationException("Invalid providerType");
}
this.context = new ApplicationContext(dbContextBuilder);
}
return this.context;
}
public void Dispose(){
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing){
if (disposing){
disposing?.Dispose();
}
}
}
Also make sure you implement the disposable pattern as show above, so the context gets disposed as soon as the factory gets disposed, to prevent the DbContext remaining in memory longer than necessary and free unmanaged resources as soon as possible.
Finally register the factory as scoped, as you would the context itself:
services.AddScopedd<IDbContextFactory, DbContextFactory>();
A more advanced and generic/extendable approach is by creating a IDbContextProxy<T> class which uses a bit of reflection to get the correct constructor and the DbContextOptionsBuilder to it.
Also possible to create a IDbContextBuilder which abstracts the provider creation.
public class SqlServerDbContextBuilder IDbContextBuilder
{
public bool CanHandle(string providerType) => providerType == "SqlServer";
public T CreateDbContext<T>(connectionString)
{
T context = ... // Create the context here
return context;
}
}
Then you can pick the correct provider w/o a hard coded if/else or switch block just by doing
// Inject "IEnumerable<IDbContextBuilder> builders" via constructor
var providerType = "SqlServer";
var builder = builders.Where(builder => builder.CanHandle(providerType)).First();
var context = builder.CreateDbContext<ApplicationContext>(connectionString);
and adding new types of provider is as easy as adding the dependencies and an XxxDbContextBuilder class.
See here, here or here for more information about this and similar approaches.
I think you can use repositories which are using a db context you specified and you can pass a parameter to context constructor to choose the endpoint. I am not sure on this but it might work for your situation.
I followed this article for repository pattern, I recommend to read it :)
http://cpratt.co/generic-entity-base-class/
I have an ASP.NET 5 MVC 6 application. It has a Data Access library which needs a connection string to make a connection to the database.
Currently I am passing a strongly typed configuration settings class with connection string as a public property all the way up from the MVC controllers (Where it is received through DI) to the Data Access Class library.
I want to know if there is a better way for a class library to access strongly typed configuration settings using dependency injection or any other mechanism ?
Thank you.
EDIT : Code Example
This is a generic DbTransaction class which is called from the business layer.
public class DbTransactions<TEntity> where TEntity : DbEntity, new()
{
private readonly Query _query;
public DbTransactions(string connectionString)
{
_query = new Query(connectionString);
}
public TEntity GetById(long id)
{
var sqlGenerator = new SqlGenerator<TEntity>();
var sql = sqlGenerator.GetSelectByIdQuery();
var mapper = new NMapper.Mapper<TEntity>();
var cmd = _query.GetNpgsqlCommand(sql, new { id });
return mapper.GetObject(cmd);
}
}
The query class creates the connection object from the connection string that is provided to it.
I agree with #Steven that using IOptions<T> is a bad idea. You can however use the ConfigurationBinder extensions to read out a specific section of configuration into a strongly-typed POCO class. Just make sure you have this somewhere in your project.json's dependencies section:
"dependencies": {
[other dependencies],
"Microsoft.Extensions.Configuration.Binder": "1.0.0-rc1-final",
[other dependencies]
}
Just build up your configuration as normal. For example, say you had a Database.json configuration file that looked like this:
{
"Database": {
"ConnectionInfo": {
"connectionString": "myConnectionString"
}
}
}
You can build your configuration from the Startup method in Startup.cs:
public IConfiguration Configuration { get; private set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) {
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.SetBasePath(appEnv.ApplicationBasePath)
.AddJsonFile("Database.json")
.AddEnvironmentVariables()
Configuration = configBuilder.Build();
}
Now we can make a POCO class to match the "Database:ConnectionInfo" section of the JSON configuraiton file. You can match it to an interface as #janhartmann suggests, but it may or may not be necessary.
public class DatabaseConnectionInfo {
public string ConnectionString { get; set; }
}
Now, how can we get that DatabaseConnectionInfo class populated with the data from the JSON config file? One way is to use the IOptions<T> framework type, but I don't like using framework types when I can avoid them. Instead, you can get an instance like so:
DatabaseConnectionInfo dbConnInfo = Configuration
.GetSection("Database:ConnectionInfo")
.Get<DatabaseConnectionInfo>();
Now you can just register the dbConnInfo type as a singleton of the type DatabaseConnectionInfo (or as a singleton of an interface type if you prefer to have an immutable configuration settings object). Once it's registered in the IoC container, you can constructor inject it where needed:
public class DbTransactions<TEntity> where TEntity : DbEntity, new()
{
private readonly Query _query;
public DbTransactions(DatabaseConnectionInfo dbConnInfo)
{
_query = new Query(dbConnInfo.ConnectionString);
}
public TEntity GetById(long id) { ... }
}
You can let your service class depend on a an interface, e.g.:
public interface IConnectionFactory {
string ConnectionString();
}
public class MyDataAccessClass {
private readonly IConnectionFactory _connectionFactory
public MyDataAccessClass(IConnectionFactory connectionFactory) {
_connectionFactory = connectionFactory;
}
public void Whatever() {
var connectionString = _connectionFactory.ConnectionString();
}
}
And then make an implementation of it (as near to your composition root as possible):
public class SqlConnectionFactory : IConnectionFactory {
public string ConnectionString() {
return "myConnectionString";
}
}
Let the interface have the methods or properties you need.
Wire like:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConnectionFactory, SqlConnectionFactory>();
}
I use a similar method to some of those listed earlier, but I think its sufficiently different to warrant another answer.
Firstly I define an interface with all the configuration that my class needs. In this case
public interface IDbTransactionsConfiguration {
string ConnectionString { get; }
}
Then I alter my class to take this configuration via constructor injection
public class DbTransactions<TEntity> where TEntity : DbEntity, new() {
public DbTransactions(IDbTransactionsConfiguration configuration) {
...
}
}
Then I define a class that handles all the configuration for my application.
public class MyApplicationConfiguration : IDbTransactionsConfiguration, ISomeOtherConfiguration, etc {
public string ConnectionString { get; }
... other configuration
}
Then I pass this class into all classes that need it using some kind of Depenendency Injection (normally Castle Windsor or AutoFac for me).
If it is too difficult to construct DbTransactions for legacy type reasons, I define a static version of MyApplicationConfiguration and access this directly.
More details on this blog post.
I created a web starter application with VS 2015 ctp, and I would like to add an in-memory store to do some test, but when I try to read the data, I get this message
The data stores 'SqlServerDataStore' 'InMemoryDataStore' are
available. A context can only be configured to use a single data
store. Configure a data store by overriding OnConfiguring in your
DbContext class or in the AddDbContext method when setting up
services.
How can I do to create a second datastore? Now I have this row in the ConfigureService method
AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<ApplicationDbContext>()
.AddInMemoryStore()
.AddDbContext<WorkModel>( options => { options.UseInMemoryStore(persist: true); });
Edit:
Looks like the scenario is not clear.
I have the identy sql server dbcontext, and I want to add a second dbcontext, totally separated, that I want to run in memory. I'm looking how configure two different dbcontext, in this case using two different datastores.
The first one is the Identity ApplicationDbContext, and another is something like this:
public class WorkModel : DbContext
{
public DbSet<Cliente> Clienti { get; set; }
public DbSet<Commessa> Commesse { get; set; }
protected override void OnModelCreating(ModelBuilder builder) {
builder.Entity<Cliente>().Key(cli => cli.ClienteId);
builder.Entity<Commessa>().Key(cli => cli.CommessaId);
}
}
Or whatever custom dbcontext do you like
It's possible use one DbContext type with multiple data stores. It won't however play well with your calls to .AddDbContext(). Here is an example of how to do it taking .ConfigureServices() entirely out of the picture.
class MyContext : DbContext
{
bool _useInMemory;
public MyContext(bool useInMemory)
{
_useInMemory = useInMemory;
}
protected override void OnConfiguring(DbContextOptions options)
{
if (_useInMemory)
{
options.UseInMemoryStore(persist: true);
}
else
{
options.UseSqlServer();
}
}
}
You can then instantiate your context specifying which provider to use.
var inMemoryContext = new MyContext(useInMemory: true);
var sqlServerContext = new MyContext(useInMemory: false);
You probably need to do something like this instead...
// Add first DbContext here
AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<ApplicationDbContext>();
// Add second DbContext here
AddEntityFramework(Configuration)
.AddInMemoryStore()
.AddDbContext<WorkModel>(options => { options.UseInMemoryStore(persist: true); });
In my case, when I needed to create two SQL Contexts, I had to do this...
AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<ApplicationDbContext>()
.AddDbContext<AnotherDbContext>();
In my case, I'm using Entity Framework Core 1.0.1 with ASP.NET Core 1.0.1 and I want to run the same project on Windows and OS X but with different databases. So this is working for me:
Added some logic in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("WindowsConnection")));
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("OSXConnection")));
}
}
Then defined the two different connection strings in appsettings.json:
"ConnectionStrings":
{
"WindowsConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-{dbname}-{dbguid};Trusted_Connection=True;MultipleActiveResultSets=true",
"OSXConnection": "Data Source=\/Users\/{username}\/Documents\/{projectname}\/bin\/debug\/netcoreapp1.0\/{dbname}.db"
}
And added this logic to ApplicationDbContext.cs:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
optionsBuilder.UseSqlite("Filename=./{dbname}.db");
}
}
If you're dbcontext has
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings.
optionsBuilder.UseSqlServer(<connection string>);
}
then you can avoid the double configuration by adding a guard
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. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings.
optionsBuilder.UseSqlServer(<connection string>);
}
}
and then have a class that extends your DBContext which uses the inmemory provider, and you instantiate explicitly for your tests. Then you can pass in to the target classes, and avoid the double provider issue.