Entity Framework Core 1.0 Connection Strings - c#

We are working on a vary large ASP.NET Core MVC 1.0 application. We have 4-tiers to each of our applications as follows:
DTO
Repository (Entity Framework - Code First)
Service (Business Logic)
MVC (UI-MVC)
Currently, in our repositories, which handle all database operations we have hard coded the database connection strings in the DbContext as follows:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseSqlServer("Data Source=somedatabase.database.windows.net;Initial Catalog=database;Integrated Security=False;User ID=username;Password=password;Connect Timeout=60;Encrypt=True;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;MultipleActiveResultSets=true");
}
This project is outside the MVC project as a standalone ASP.NET Core 1.0 project. It also has a empty Program.cs file in it which seems to be required to execute the code-to-database command lines (dotnet ef migrations add and dotnet ef database update).
The reason we have a hard coded connection string in the DbConext is because when we use the following code, we get an object reference not set to an instance to an object exception, when executing the dotnet ef commands.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings["StandardDatabase"].ConnectionString);
}
However, since we have a Program.cs, if we add a Debug.WriteLine for the connection string and run the project, it does return the correct connections string and if we set the connection string in the appsettings.json file in the UI, the UI will successfully connect as well.
THE ISSUE:
The above mentioned stack is what we use for several "Micro Apps", which means we have several projects that connect to several databases. We also want to take advantage of Development, Staging and Production connection strings.
If we use Configuration Manager Connection String, everything is good for daily operations; however, when ever we want to utilize Entity Frameworks code to database command lines, we need to go in to each repository we want to update and change the DbContext to a hard coded connection string, execute the commands, then change them back to when done, which becomes quite troublesome.
THE QUESTION:
Are we just doing this wrong, is there a preferred practice for setting up an Entity Framework Core 1.0 stack which allows us not to manually have to change the DbContext but take advantage of configuration files across the board?
Any direction would be appreciated!

EF Core is intended to be configured via dependency injection. Dependency injection keeps your DbContext clean, and independent of implementation details of the environment.
Your initial solution of hard-coding connection strings tightly coupled the DbContext to the knowledge of where the database is located. That's obviously a problem. But your proposed solution tightly couples the DbContext to the knowledge of a particular configuration file. That, too, is a problem.
To keep the DbContext independent of environmental details, create a constructor that takes a DbContextOptions parameter and calls the base class constructor.
public class MyContext : DbContext
{
public MyContext(DbContextOptions options) :
base(options)
{
}
}
Do this instead of overriding OnConfiguring. Then initialize it in the Startup.cs of your host application. That's where the knowledge of the configuration file belongs.
public class Startup
{
private IConfigurationRoot _configuration;
public Startup(IHostingEnvironment env)
{
_configuration = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json")
.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfigurationRoot>(_configuration);
services.AddDbContext<MyContext>(options => options
.UseSqlServer(_configuration.GetConnectionString("MyContext")));
}
}
Now you can use your DbContext from anywhere.

ANSWER: I was making this much more difficult then it actually was. I followed Juunas' advise and added in the following code in my Repository DbContext Class:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// get the configuration from the app settings
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
// define the database to use
optionsBuilder.UseSqlServer(config.GetConnectionString("StandardDatabase"));
}
Which works perfect with the dotnet ef command line tools and far as the multiple environment setup goes with my MVC UI sticking with the following default code in my startup.cs works great as well.
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("project.json", optional: true, reloadOnChange: true);

IDbContextFactory might also help. EF Command Line Tools and DI can use this factory to create instances of your DBContext. Design Time services (e.g. Migrations) will discover implementations of this interface that are in the same assembly as the derived context.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace MyProject
{
public class BloggingContextFactory : IDbContextFactory<BloggingContext>
{
public BloggingContext Create()
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlite("Filename=./blog.db");
return new BloggingContext(optionsBuilder.Options);
}
}
}

Related

ASP.NET Core : Why do we need IDesignTimeDbContextFactory?

I have an ASP.NET Core application and all I have is the DataContext, I don't have IDesignTimeDbContextFactory implemented.
public class DataContext : DbContext, IUnitOfWork
{...}
With that I can do Add-Migration, Update-Database & Script-Migration.
However, I came across an another project where they have implemented IDesignTimeDbContextFactory, mentioned that this is to generate migration classes.
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<CodingBlastDbContext>
{
public CodingBlastDbContext CreateDbContext(string[] args)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<CodingBlastDbContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
builder.UseSqlServer(connectionString);
return new CodingBlastDbContext(builder.Options);
}
}
I wonder why this is needed? especially the first project works without implementing the IDesignTimeDbContextFactory..
Docs have some explanation on when you can leverage the design-time factory:
A design-time factory can be especially useful if you need to configure the DbContext differently for design time than at run time, if the DbContext constructor takes additional parameters are not registered in DI, if you are not using DI at all, or if for some reason you prefer not to have a CreateHostBuilder method in your ASP.NET Core application's Main class.
The only use case I personally encountered was when DbContext was moved into a separate library and we did not want to run CreateHostBuilder for context designing purposes (startup involved some relatively heavy stuff and we didn't want to invoke that). Like for example here.

Is it possible to pass the ServiceProvider into a constructor parameter?

Problem:
We have a .NET 5 WPF application that has an EntityFramework Core entities class file DbEntities, which implements the DbContext. We use constructor injection when instantiating it. One of the options that we use is AddInterceptors in order to append an Access Token to the SqlConnection. The interceptor is called AzureAuthenticationInterceptor. When registering the service, we would like to pass in the ServiceProvider so that it is available in the interceptors constructor, which can be used to get a service that implements Access Token in-memory caching.
The reason for it is that we have a project with 50+ classes that all use the same DbEntities file, which takes 0 arguments in the constructor. This was upgraded to .NET 5 where Dependency Injection was avoided due to the work it would take to apply it to all of the forms. So, the DbEntities is instantiated in the forms with new DbEntities();.
But, in this case, we are implementing an access token cache, which needs to be registered as a service. Otherwise, if we just instantiate the cache every time we create a new DbContext, then the cache will be wiped out.
The access token in-memory cache is implemented using this method https://mderriey.com/2020/09/12/resolve-ef-core-interceptors-with-dependency-injection/
We only want to use dependency injection for the in-memory token cache. The only way we think of as a shortcut is to pass the ServiceProvider in the interceptor's constructor, but it does not appear available in the ConfigureServices method.
Question:
Is it possible to pass in the ServiceProvider? If not, is there any other way we can implement dependency injection on the interceptor without having to change 50 class files?
Program.cs
Public static void Main()
{
...
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((context, builder) =>
{
builder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
.ConfigureServices((context, services) =>
{
Configuration = context.Configuration;
ConfigureServices(Configuration, services);
})
.Build();
...
}
private static void ConfigureServices(IConfiguration objConfiguration, IServiceCollection objServices)
{
objServices.AddMemoryCache()
.AddSingleton<IAzureSqlTokenProvider, AzureIdentityAzureSqlTokenProvider>()
.Decorate<IAzureSqlTokenProvider, CacheAzureSqlTokenProvider>()
.AddSingleton(new AzureAuthenticationInterceptor(IServiceProvider_NeededHere))
;
}
DbEntities.cs
public DbEntities() :
base(new DbContextOptionsBuilder<DbEntities>()
.UseSqlServer(ConfigurationManager.ConnectionStrings["DbEntities"].ConnectionString)
.AddInterceptors(new AzureAuthenticationInterceptor())
.Options)
{ }
AzureAuthenticationInterceptor.cs
public AzureAuthenticationInterceptor(IServiceProvider objServiceProvider)
{
this.IAzureSqlTokenProvider = (IAzureSqlTokenProvider)objServiceProvider.GetService(typeof(IAzureSqlTokenProvider));
}
First, avoid injecting IServiceProvider, it is a code smell and leads to poor design.
Refactor AzureAuthenticationInterceptor.cs
public AzureAuthenticationInterceptor(IAzureSqlTokenProvider tokenProvider) {
this.IAzureSqlTokenProvider = tokenProvider;
}
So that way explicit dependencies can be injected as needed
//...
.AddSingleton<AzureAuthenticationInterceptor>()
//...
When resolving the interceptor while configuring the DbEntities
//...
services.AddDbContext<DbEntities>((provider, options) => {
options.UseSqlServer(Configuration.GetConnectionString("<connection-string-name>"));
options.AddInterceptors(provider.GetRequiredService<AzureAuthenticationInterceptor>());
});
//...
Note that if you are manually initializing the context using the default constructor, ie:new DbEntities(); Then this bypasses the opportunity to apply dependency injection via constructor injection.

Pass connection string to .Net Core EF library from another library

I have an ASP.NET Core application, which is structured in three layers i.e.:
Data access layer (Entity Framework)
Business logic layer
ASP.NET MVC web application
As it is right now, the configuration works, and I can access my database in my web application library. However, when I constructed my data access layer with EF Db first, I got a generic class, which looks something like this:
public partial class ClassContext: DbContext
{
public ClassContext(DbContextOptions<ClassContext> options)
: base(options)
{
}
public virtual DbSet<Entity> Entity{ get; set; }
....
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("ConnString");
}
}
}
As you can see, the connection string is hardcoded into my OnConfiguring method, which is not recommended.
Therefore, I followed the following "guide" here, which suggest me to use the built in DI, to pass the connection string from my Web library.
This I did:
public void ConfigureServices(IServiceCollection services)
{
//Add connectionstring to EF
services.AddDbContext<ClassContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ConnectionStringFromAppSettings")));
}
According to the "guide" the connectionstring should be provided to the constructor of my ClassContext class - and it sort of does, however not with any connection string..
This means, that optionsBuilder.IsConfigured evaluates false, and wants to use the hard coded connectionstring.
Therefore, I would very much like to know, if I use the DI incorrect, since i cannot access the connection string in my ClassContext class
Update
I removed the OnConfiguring() method, and do now inject the context into my service class constructor in the business logic layer the following way:
public MasterService(ClassContext context)
{
MinorService = new MinorService(context);
}
public Stuff AddStuffIntoDatabase(Stuff test)
{
//business logic going before here
MinorService.addstuffMethod(test)
}
However, now I get the following error, when I want to do an operation in my database:
No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider.
This did not happen before, when I configured the connectionstring wrongly in my OnConfiguring() method.
What about the appsettings.json? Is it like this?
"ConnectionStrings": {
"ConnectionStringFromAppSettings": "your_connection_string"
}
Configuration.GetConnectionString always search for the section "ConnectionStrings"
https://learn.microsoft.com/it-it/dotnet/api/microsoft.extensions.configuration.configurationextensions.getconnectionstring?view=dotnet-plat-ext-3.1

How can I add a dbContext after Startup in .Net Core?

I am using .Net Core, using the built-in dependency injection. In my login screen, I need the user to also choose a departmental database - we have different databases with the same structure to use the same application with different data. However, I can't figure out how to add/modify the dbContext that late. Startup.cs has the DI, but I don't know which connection string to read from the config until the user has chosen the department. It is a small database, and the company is not concerned about the management of the duplicate databases.
How can I add the service late
services.AddDbContext<my_accountingContext>(options =>
options.UseMySQL(Configuration.GetConnectionString("CorrectDepartmentConfig")));
when I actually know what CorrectDepartmentConfig is?
Or, if that can't be done, how can I do a smelly change of the my_accountingContext after Startup.cs?
You can use an implementation factory overload of IServiceCollection in ConfigureServices method form Startup class:
//First register a custom made db context provider
services.AddTransient<ApplicationDbContextFactory>();
//Then use implementation factory to get the one you need
services.AddTransient(provider => provider.GetService<ApplicationDbContextFactory>().CreateApplicationDbContext());
The implementation of CreateApplicationDbContext depends on your specific needs, but a base implementation should look like the following:
public ApplicationDbContext CreateApplicationDbContext(){
//TODO Something clever to create correct ApplicationDbContext with ConnectionString you need.
}
After this implementation, you can inject the correct ApplicationDbContext in your controller, action...
public MyController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public IActionResult([FromServices] ApplicationDbContext dbContext){
}
You can always set the connection string from inside the protected OnConfiguring method. You can get access to the IConfiguration instance from there (the DbContext class has a service locator, Instance property), retrieve the connection string, and then call UseMySql extension method with the appropriate connection.
Something like this:
protected virtual void OnConfiguring(DbContextOptionsBuilder builder)
{
var configuration = (this as IInfrastructure<IServiceProvider>).GetService<IConfiguration>();
var connectionString = configuration.GetConnectionString("<name>");
builder.UseMySql(connectionString);
base.OnConfiguring(builder);
}
For the strongly-typed version of GetService do not forget to reference namespace Microsoft.Extensions.DependencyInjection.

.Net Core 2.1 Web and Console DbContexts

Im not known for my clarity when asking questions, so forgive me, also i have no formal training for any of this but im stumped.
I am mid upgrade from .Net 4.7.1 to .Net Core 2.1, my solution consists of 2 parts, an IIS Web Application for MVC, and a Console Application, the IIS App displays data, and the console application does all the actual processing.
Before i started this port for my console app when i needed stuff from the database i would simply
using (var db = new ApplicationDbContext())
{
SomethingModel model = db.X.First(x => x.Whatever == whatever);
}
And just like that i have the data i want from the database, butttt do you think i can do that with Core 2.1 can i hell.
I got all the code ported all the refrences resolved and so far as i can tell its ready to run. Except i cant call data from the database and im stumped, google just shows code first and ef stuff, or i dont know what im really asking.
So if anyone can help its much appreciated
-- Update 1---
The error is An Object refrence is required for the non-static field, metho or property Program._db
The DbModel is defined in Data/ApplicationDbContext.cs for the IIS App and is as follows
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options)
: base(options)
{
}
-- Program.cs for Console App
class Program
{
private ApplicationDbContext _db { get; }
public Program(ApplicationDbContext context)
{
_db = context;
}
static void Main(string[] args)
{
new ExecutionEngine(_db).Run();
}
}
The previous way you wrote the code (using) was never a good idea. Your context should be request-scoped; using using can lead to all sorts of issues with entity tracking and totally destroys all the helpful caching EF does. The best method for getting a context instance was always dependency injection via a DI container.
ASP.NET Core uses dependency injection for everything, and because of this EF Core's DbContext is designed to be dependency injected. In this regard, it no longer uses a default constructor out of the box, which is why your old code is failing (it depends on there being a default constructor).
Long and short, do things right and inject your context. It looks like you're attempting to do this based on your update. However, you cannot inject into something like Program. This is the entry point for your application, which means literally nothing exists yet. If you take a look at your web app, you'll notice that Program there sets up the web host builder (using Startup) and then builds and runs it. Behind the scenes this is doing a bunch of stuff, including setting up the service collection. This is what you need to do in your console app (set up the service collection). That's relatively straight forward:
class Program
{
static void Main(string[] args)
{
var serviceProvider = new ServiceCollection()
.AddDbContext<ApplicationDbContext>(o =>
o.UseSqlServer("connection string"))
.BuildServiceProvider();
var context = serviceProvider.GetRequiredService<ApplicationDbContext>();
new ExecutionEngine(context).Run();
}
}
Now, this is a bit of overkill just based on the code you have here. You can simply new up an instance of your context via DbContextOptionsBuilder:
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer("connection string")
.Options;
var context = new ApplicationDbContext(options);
However, using the service collection allows you to handle more advanced scenarios and better reuse your instances of things like your context across your codebase. Also it's worth mentioning that you should probably consider integrating configuration providers as well, so you don't need to hardcode your connection string. That's also relatively straight-forward:
var config = new ConfigurationBuilder()
.SetBasePath(Path.Combine(AppContext.BaseDirectory))
.AddJsonFile("appsettings.json", optional: true)
.Build();
You might also want to add environment-specific configuration:
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
Then:
.AddJsonFile($"appsettings.{environment}.json", optional: true);
This is just the same as doing all this in a web app, so you can add whatever type of configuration you like.

Categories

Resources