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
Related
I've writing various tests for my app and now I got to a problem that I'm unable to solve.
The test I'm writing is a simple command that executes and action that modifies a database and then a query to validate that the values are correct.
In order to connect to the database my BaseContext gets the connection string from an interface:
public BaseContext(DbContextOptions options, IConnectionStringProvider connectionStringProvider)
: base(options)
{
_connectionStringProvider = connectionStringProvider;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
var connString =_connectionStringProvider.GetConnectionString();
optionsBuilder.UseOracle(connString);
}
}
My connection string provider interface looks like this:
public interface IConnectionStringProvider
{
string GetConnectionString();
Dictionary<string, string> GetConnectionSession();
}
And the implementation looks like this:
public class HttpConnectionStringProvider : IConnectionStringProvider
{
public HttpConnectionStringProvider(IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
{
_configuration = configuration ?? throw new NullReferenceException(nameof(configuration));
_httpContext = httpContextAccessor.HttpContext ?? throw new NullReferenceException("HttpContext");
}
public string GetConnectionString()
{
// Do something with http context and configuration file and return connection string
}
}
All this interfaces are registered using autofac.
Then when executing the following test:
[Test]
public async Task AddProductTest()
{
string connectionString = "fixed_connection_string";
var mock = new MockRepository(MockBehavior.Default);
var mockConnectionStringProvider = new Mock<IConnectionStringProvider>();
mockConnectionStringProvider.Setup(x => x.GetConnectionString())
.Returns(connectionString);
await ProductModule.ExecuteCommandAsync(new AddProductCommand(1, "nameProduct"));
var products = await ProdcutModule.ExecuteQueryAsync(new GetProducts(1));
// Assert
Assert.That(products .Status, Is.EqualTo(ResultQueryStatus.Ok));
Assert.That(products .Value, Is.Not.Empty);
Assert.That(products .Value.Any(x => x.Name== "nameProduct"));
}
The IConfiguration and IHttpContextAccessor are mocked using NSubstitute library. But even if I deleted them just to test if IConnectionStringProvider returned the value expected in setup it didn't work.
When running the test in debug I see that steps into the method GetConnectionString() when it should be mocked. I don't know what I'm doing wrong I suppose there is something that I don't understand about testing.
I am curious what you are attempting to do:
Mocking out the connection string for creating someone like a DB connection string or similar, doesn't make a lot of sense. It is part of the base functionality of the DI bits for C# service handling in Setup.
if you want to mess with this, of course you can do it, but the purpose escapes me a bit.
However, if you simply wish to test ProductModule as something you have written, then it makes more sense.
Then when you make this statement:
ProductModule productModule = new ProductModule( - parameters - );
your parameters likely requires a IConnectionStringProvider.
There you need to use your mockConnectionStringProvider.Object
That will allow you to insert your mocked object, as the object used in your ProductModule constructor.
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);
I have tried to figure this out, but I am stuck.
I have a Net Core 2 application with Service/Repo/Api/Angular layers - but now I want to 'bolt on' a console application and access all the goodies I have already built up. I seem to be in a mess of static objects and DI and null parameters. Anyway, here is a simplified version of my code.
namespace SimpleExample
{
class Program
{
private static ApplicationDbContext _appDbContext;
public Program(ApplicationDbContext appDbContext)
{
_appDbContext = appDbContext;
}
static void Main(string[] args)
{
var instance = new Program(); // this doesn't work!
var instance = new Program(_appDbContext); // neither does this!
instance.GetData();
}
private void GetData()
{
Console.WriteLine("Let's read some data! Press a key to continue.");
Console.ReadLine();
var data = "my data";
var result = GetId(data);
}
private string GetId(string original)
{
var data = _appDbContext.Data
.Where(x => x.Name == original.Trim)
.FirstOrDefault();
return data;
}
}
}
I am getting the classic
'An object reference is required for the non-static field'
error. Then from investigating on here I changed things to static and then everything becomes null.
It's not just the DbContext I am trying to inject. I'm also trying to inject
private ManagerService _managerService;
but getting same errors.
Update
If I try
private static ApplicationDbContext _appDbContext = new
ApplicationDbContext();
as suggested a few times below, then I get the error
There is no argument given that corresponds to the required formal
parameter 'options' of
'ApplicationDbContext.ApplicationDbContext(DbContextOptions)'
OK, I have figured this out, and I'll post my answer for anyone else struggling in this situation.
When you launch the console app, your normal startup.cs doesn't execute, so you have to put a lot of that code in your console app.
private static SiteService _siteService;
private static ApplicationDbContext _appDbContext;
public static void Main()
{
var services = new ServiceCollection();
services.AddTransient<ISiteInterface, SiteRepo>();
services.AddTransient<SiteService>();
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer("blah-blah"));
var serviceProvider = services.BuildServiceProvider();
_siteService = serviceProvider.GetService<SiteService>();
_appDbContext = serviceProvider.GetService<ApplicationDbContext>();
GetData();
}
and now your _appDbContext will be available throughout the rest of your console app.
Hope that helps!
Basically, if you do not plan extensive usage of DbContext nor use DI, there is no need for ServiceProvider. Just remember to make DbContext instance short living and use it for single unit-of-work, not longer.
Your context may look like this:
using Microsoft.EntityFrameworkCore;
namespace YourNamespace;
public class ApplicationContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Your conn string");
}
public DbSet<YourType> YourEntity { get; set; }
}
You can pass conn string by ApplicationContext ctor as well. This is nicely explained here by Microsoft .
Then you can utilise your ApplicationContext like this:
// Unit-of-work closed in using statement
// Here you can query/update your DbContext
using (var dbContext = new ApplicationContext())
{
var queryResult = dbContext.YourEntity.Where(....);
}
You can prepare number of such units-of-work as separate methods for querying a database.
Your repository service can consist of these methods.
Then you can instantiate the service as needed.
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.
I am having a multitenant application which uses same web app with multiple Customer databases and having shared DbContext. When user logs in based on his credentials I provide connection string to DbContext and load the data. Problem occurs when multiple user logs in at the same time and they can see each others data as DbContext keeps on switching between different users.
I am using EF 5.0 and Autofac IOC mainly. Whats the best way to manage this?
How can I have my DbContext maintain its data for that specific user and will not change even though other users logged in and will have different database contexts for them?
Here's code in my Login page,
protected void LoginBtn_Click(object sender, EventArgs e)
{
int i = Convert.ToInt32(CustomerId.Text);
var builder = new ContainerBuilder();
var profile = _profileProvider.GetCustomerProfile(i);
ConnectionStringManager.ConnectionString = profile.connString;
builder.RegisterModule<CustomerDataModule>();
builder.Update(_container);
Response.Redirect("Home.aspx");
}
This is my static variable which gives connection string
public static class ConnectionStringManager
{
public static string ConnectionString { get; set; }
}
Here's my Module which has all entities and context classes,
public class CustomerDataModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(c =>
{
ConnectionString = ConnectionStringManager.ConnectionString;
return new CustomerDataContext(ConnectionString);
})
.As<ICustomerDataContext>()
.As<IDbContext>()
.InstancePerDependency();
builder.RegisterType<CustomerDataContextFactory>().As<ICustomerDataContextFactory>();
var assembly = Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces()
.InstancePerDependency();
}
}
Here's my DbContext,
public partial class CustomerDataContext: DbContext, ICustomerDataContext
{
public CustomerDataContext(string connectionString)
: base(connectionString)
{ }
.......
Here's my One of the repository,
public class CustomerRepository : GenericRepository<Customer, int>, ICustomerRepository
{
public CustomerRepository(ICustomerDataContext context)
: base(context, customer => customer.Id)
{
}
}
a static variable in a web app means sharing its data between all users at the same time. you need to move that value to a session variable, which is specific to each user.
var builder = new ContainerBuilder();
// will add `HTTP request lifetime scoped` registrations (InstancePerHttpRequest) for the HTTP abstraction classes
builder.RegisterModule(new AutofacWebTypesModule());
builder.Register<MyType>(ctx =>
{
var sessionBase = ctx.Resolve<HttpSessionStateBase>();
//now use the session value to retrieve the connection string
});