How Do I Create a DbContextFactory Within a Blazor Background Service? - entity-framework-core

I am working on my first Blazor Server application, which is also my first Entity Framework Core application. I am wanting to set up a background service which, once a day in the early morning, checks the database to see if any of a certain record type has been added with yesterday's date. If so, the relevant records are pulled, formatted, and then emailed to a stakeholder.
I have the EF, formatting, and emailing code working just fine when I trigger the report by manually visiting the page. The problem that I have is how to provide the background service with a DbContextFactory so that the EF and related code can execute.
Up to this point I've always used Razor-based dependency injection to inject the IDbContextFactory via an inject IDbContextFactory<OurAppContext> DbFactory at the top of the page, and then accessed the DbFactory via the DbFactory variable.
However, background services are (according to this Microsoft tutorial) set up through Program.cs, so I don't have access to Razor-based dependency injection there.
I have set up my background service (what I call the PhaseChangeReportService) as indicated in the above link, and it dutifully outputs to the console every 10 seconds that it is running with an updated execution count. I don't fully understand what's going on with the various layers of indirection, but it appears to be working as Microsoft intended.
I noted that the constructor for the background service takes in an ILogger as a parameter, specifically:
namespace miniDARTS.ScopedService
{
public sealed class PhaseChangeReportService : IScopedProcessingService
{
private int _executionCount;
private readonly ILogger<PhaseChangeReportService> _logger;
public PhaseChangeReportService(ILogger<PhaseChangeReportService> logger)
{
_logger = logger;
}
public async Task DoWorkAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
++_executionCount;
_logger.LogInformation("{ServiceName} working, execution count: {Count}", nameof(PhaseChangeReportService), _executionCount);
await Task.Delay(10_000, stoppingToken);
}
}
}
}
I was (and am) confused that the constructor is never referenced within Visual Studio, but when I drop a breakpoint on its one line of code it is hit. I tried modifying this constructor's signature so that it took in an IDbFactory<OurAppContext> as well, so that whatever dark magic is allowing an ILogger<BackgroundServiceType> to come in for assignment to _logger might bring in a DbFactory<OurAppContext> as well, like so:
private readonly ILogger<PhaseChangeReportService> _logger;
private readonly IDbContextFactory<miniDARTSContext> _dbContextFactory;
public PhaseChangeReportService(ILogger<PhaseChangeReportService> logger, IDbContextFactory<miniDARTSContext> dbContextFactory)
{
_logger = logger;
_dbContextFactory = dbContextFactory;
}
However, doing so just led to the constructor breakpoint being skipped over and not breaking, with no exception being thrown or any console output of any kind (i.e. the prior execution count console output no longer showed up). So, I gave up on that approach.
Here is the relevant section of Program.cs:
// Configure the database connection.
string connectionString = builder.Configuration.GetConnectionString("miniDARTSContext");
var serverVersion = new MySqlServerVersion(new Version(8, 0, 28));
builder.Services.AddDbContextFactory<miniDARTSContext>(options => options.UseMySql(connectionString, serverVersion), ServiceLifetime.Scoped);
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<ScopedBackgroundService>();
services.AddScoped<IScopedProcessingService, PhaseChangeReportService>();
})
.Build();
host.RunAsync();
Here's IScopedProcessingService.cs:
namespace miniDARTS.ScopedService
{
public interface IScopedProcessingService
{
Task DoWorkAsync(CancellationToken stoppingToken);
}
}
And here's ScopedBackgroundService.cs:
namespace miniDARTS.ScopedService;
public sealed class ScopedBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<ScopedBackgroundService> _logger;
public ScopedBackgroundService(IServiceProvider serviceProvider, ILogger<ScopedBackgroundService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation($"{nameof(ScopedBackgroundService)} is running.");
await DoWorkAsync(stoppingToken);
}
private async Task DoWorkAsync(CancellationToken stoppingToken)
{
_logger.LogInformation($"{nameof(ScopedBackgroundService)} is working.");
using (IServiceScope scope = _serviceProvider.CreateScope())
{
IScopedProcessingService scopedProcessingService = scope.ServiceProvider.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWorkAsync(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation($"{nameof(ScopedBackgroundService)} is stopping.");
await base.StopAsync(stoppingToken);
}
}
I'm confident I'm misunderstanding something relatively fundamental here when it comes to services / dependency injection, but my Googling and review of past StackOverflow answers has not turned up anything I can run with.

The IDbContextFactory is an interface that is used for creating instances of a DbContext. When you add it to your services on program.cs for Blazor (services.AddDbContextFactory(parameters)), it implements the IDbContextFactory for you. This allows you to use the #inject IDbContextFactory<YourDbContext> DbFactory at the top of your razor components and then within your code you can call the CreateDbContext method when you need to create an instance of the DbContext (ex. using var context = DbFactory.CreateDbContext()).
You can pass an injected DbContextFactory as a parameter from a razor component to a class, and then use that DbContextFactory in a method to create an instance of the DbContext (see constructor injection), but that still relies on the razor component to inject the DbContextFactory to begin with.
To create an instance of a DbContext independent of a razor component, you need to use the constructor for your DbContext. Your DbContext will have a public constructor with a DbContextOptions parameter (this is required to be able to use AddDbContextFactory when registering the factory service in program.cs). You can use this constructor to implement your own factory. If you aren't sure which options to use, you can check your program.cs to see what options you used there.
public class YourDbFactory : IDbContextFactory<YourDbContext>
{
public YourDbContext CreateDbContext()
{
var optionsBuilder = new DbContextOptionsBuilder<YourDbContext>();
optionsBuilder.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Test"));
return new YourDbContext(optionsBuilder);
}
}
Once you've created your own implementation of the IDbContextFactory interface, you can then use it in your code independent of razor components - for example in the background service class.
YourDbFactory DbFactory = new YourDbFactory();
using var context = DbFactory.CreateDbContext();

Related

EF Core Migrations in Azure Function startup

According to https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection the service provider should not be used until AFTER the startup has completed running. Indeed, if I try to get a registered service it will fail.
Example:
[assembly: FunctionsStartup(typeof(Startup))]
namespace Fx {
public sealed class Startup : FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) {
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddEnvironmentVariables();
var configuration = configurationBuilder.Build();
builder.Services.AddInfrastructure(configuration);
builder.Services.AddApplication();
var serviceProvider = builder.Services.BuildServiceProvider();
DependencyInjection.AddDatabase(serviceProvider).GetAwaiter().GetResult();
}
}
}
public static class DependencyInjection {
public static async Task AddDatabase(IServiceProvider services) {
using var scope = services.CreateScope();
var serviceProvider = scope.ServiceProvider;
var context = serviceProvider.GetRequiredService<ApplicationDbContext>();
//Error generated here
if (context.Database.IsSqlServer()) {
await context.Database.MigrateAsync();
}
await ApplicationDbContextSeed.SeedSamplePersonnelDataAsync(context);
}
public static IServiceCollection AddInfrastructure(
this IServiceCollection services,
IConfiguration configuration) {
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
return services;
}
}
This produces the following error
Microsoft.EntityFrameworkCore: 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. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
Is there a good option for migrating and seeding during startup?
The easiest way I found to run code after startup was by registering a custom IWebJobsStartup by using the WebJobsStartupAttribute (the FunctionsStartupAttribute actually also inherits from this attribute). In the WebJobsStartup class you'll need to register your extension using the AddExtension where you are able to use dependency injection and seed your database. My code:
[assembly: WebJobsStartup(typeof(DbInitializationService), "DbSeeder")]
namespace Our.Database.Seeder
{
public class DbInitializationService : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
builder.AddExtension<DbSeedConfigProvider>();
}
}
[Extension("DbSeed")]
internal class DbSeedConfigProvider : IExtensionConfigProvider
{
private readonly IServiceScopeFactory _scopeFactory;
public DbSeedConfigProvider(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public void Initialize(ExtensionConfigContext context)
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetService<YourDbContext>();
dbContext.Database.EnsureCreated();
// Further DB seeding, etc.
}
}
}
According to your code, I assume that you're building something aligned to the CleanArchitecture Repository on Github. https://github.com/jasontaylordev/CleanArchitecture
The main difference between this repo and your apporach, is that you're obviously not using ASP.NET, which is not a problem at all, but requires a little bit more configuration work.
The article already mentioned (https://markheath.net/post/ef-core-di-azure-functions) refers another blogpost (https://dev.to/azure/using-entity-framework-with-azure-functions-50aa), which briefly explains that EntityFramework Migrations are not capable of auto-discovering your migrations in an Azure Function. Therefore, you need to implement an instance of IDesignTimeDbContextFactory. I also stumbled upon it in the microsoft docs:
https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-a-design-time-factory
You could for example place it inside your Infrastructure\Persistence\Configurations folder. (Once again, I'm only assuming that you're following the CleanArchitecture repo structure)
DI in AZURE Functions
Caveats
A series of registration steps run before and after the runtime processes the startup class. Therefore, keep in mind the following items:
The startup class is meant for only setup and registration. Avoid using services registered at startup during the startup process. For instance, don't try to log a message in a logger that is being registered during startup. This point of the registration process is too early for your services to be available for use. After the Configure method is run, the Functions runtime continues to register additional dependencies, which can affect how your services operate.
The dependency injection container only holds explicitly registered types. The only services available as injectable types are what are setup in the Configure method. As a result, Functions-specific types like BindingContext and ExecutionContext aren't available during setup or as injectable types

Get generated SQL for a DbContext.SaveChanges in Entity Framework Core

In Entity Framework Core, is it possible to see the SQL that will be applied when the SaveChanges() method is called on the DbContext?
EF Core 5.0+
There are a simple builtin solution, add the following function to the DbContext class.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
this will log both queries and commands.
See here for mote details: Simple Logging
Here are the docs on creating a LoggerFactory in Core 3. In short:
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
.AddConsole()
.AddEventLog();
});
You may need to add a reference to Microsoft.Extensions.Logging.Console.
Use the DbContextOptionsBuilder to enable logging for a context.
optionsBuilder.UseLoggerFactory(loggerFactory)
I'll repeat the warning from here:
It is very important that applications do not create a new ILoggerFactory instance for each context instance. Doing so will result in a memory leak and poor performance.
Therefore, they recommend using a singleton/global instance:
public static readonly ILoggerFactory MyLoggerFactory =
LoggerFactory.Create(builder => { builder.AddConsole(); });
you can use console logger "EF Core logging automatically integrates with the logging mechanisms of .NET Core "
you can read about here :
https://www.entityframeworktutorial.net/efcore/logging-in-entityframework-core.aspx
You can use DbContextOptionsBuilder.UseLoggerFactory(loggerFactory) method to log all sql output.By using constructor Injection like below
public class DemoContext : ObjContext
{
private readonly ILoggerFactory _loggerFactory;
public DemoContext() { }
public DemoContext(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseLoggerFactory(_loggerFactory);
}
}
using (var context = new DemoContext(_loggerFactory))
{
var Employees = context.Employee.ToList();
}
Or
I suggest a few other ways of viewing the SQL generated is to use reflection to create an ObjectQuery object and then call the ToTraceString() method to actually store the query results.
using (var context = new EntityContext())
{
var query = context.Customers.Where(c => c.Id == 1);
var sql = ((System.Data.Objects.ObjectQuery)query).ToTraceString();
}
Use SQL Logging
Using The DbContext.Database.Log property can be set to a delegate for any method that takes a string.
Log SQL to the Console.
using (var context = new EntityContext())
{
context.Database.Log = Console.Write;
}
Log SQL to Visual Studio Output panel.
using (var context = new EntityContext())
{
context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
}
It's a bit tricky to log EF Core's SQL in an ASP.NET MVC web app. Unlike Entity Framework, EF Core lacks the easy-to-use DBContext.Database.Log property. As #DharmaTurtle mentioned, you can use LoggerFactory.Create, and this can work, but it does create a separate ILoggerFactory than the one that the rest of the app uses for logging (one that is apparently not using appsettings.json for options.)
The following approach is required if you want to use the same log factory for DBContext that the rest of the ASP.NET MVC web app uses:
Create a derived class of DbContext (or, if this was already done, modify the existing class appropriately). Note: this example will only log SQL in Debug builds, not Release builds.
public class DbContextWithLogging : Microsoft.EntityFrameworkCore.DbContext
{
ILoggerFactory _loggerFactory;
IConfiguration _configuration; // Provides access to appsettings.json
public DbContextWithLogging(ILoggerFactory loggerFactory, IConfiguration configuration)
=> (_loggerFactory, _configuration) = (loggerFactory, configuration);
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
#if DEBUG
builder.UseLoggerFactory(_loggerFactory);
// This line causes parameter values to be logged:
builder.EnableSensitiveDataLogging();
#endif
}
}
Note: this approach is not compatible with calling AddDbContext in Startup.ConfigureServices, so if there is already a call to AddDbContext, disable/remove it. In my case, the existing derived class of DbContext had a constructor that accepted (DbContextOptions<BarreleyeDbContext> options), which I removed.
In your Startup.ConfigureServices method, configure logging (e.g. to print to console) and enable the custom DbContext:
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging((ILoggingBuilder builder) => {
builder.AddConsole();
});
// In ASP.NET Core apps, a Scope is created around each server request.
// So AddScoped<X, Y>() will recreate class Y for each HTTP request.
services.AddScoped<DbContext, DbContextWithLogging>();
... // leave the rest as before
}
Whatever uses DbContext (e.g. controllers, or 'repositories' in the Repository pattern) should obtain it automagically via constructor injection.
EF Core uses LogLevel.Information when printing the SQL, so you need the Information level to be enabled. If you look at your appsettings.json file, you'll want to see something like this:
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
In particular, EF Core log filtering can be controlled with a key like
"Microsoft.EntityFrameworkCore": "Information",
but if this key is missing, the "Microsoft" key is used instead.
This might not work! Look for a second file called appsettings.Development.json - watch out, Visual Studio might hide this file "inside" appsettings.json. If appsettings.Development.json exists, its contents override appsettings.json (at the granularity of individual keys).
Once it's working, you'll see log info that looks like this (yes, SELECT statements are logged as well as INSERT, UPDATE and DELETE):
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (0ms) [Parameters=[#__p_0='297'], CommandType='Text', CommandTimeout='30']
SELECT e.id, e.config, e.end_date, e.entity_name, e.entity_type, e.location, e.start_date
FROM entities AS e
WHERE e.id = #__p_0
LIMIT 1

Is it possible to use one database to dynamically define the ConnectionString of another?

I've reached a bit of a brick-wall with my current project.
I have three normalised databases, one of which I want to dynamically connect to; these are:
Accounts: For secure account information, spanning clients
Configuration: For managing our clients
Client: Which will be atomic for each of our clients & hold all of their information
I need to use data stored in the "Configuration" database to modify the ConnectionString that will be used to connect to the "Client" database, but this is the bit I'm getting stuck on.
So far I've generated the entities from the databases into a project by hooking up EntityFrameWorkCore Tools and using the "Scaffold-DbContext" command & can do simple look-ups to make sure that the databases are being connected to okay.
Now I'm trying to register the databases by adding them to the ServiceCollection, I have them added in the StartUp class as follows:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
services.AddDbContext<Accounts>( options =>
options.UseSqlServer(Configuration.GetConnectionString("Accounts"))
);
services.AddDbContext<Support>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Configuration"))
);
// Erm?
SelectClientDatabase(services);
}
Obviously the next stage is to dip into the "Configuration" database, so I've been trying to keep that contained in "SelectClientDatabase()", which just takes the IServiceCollection as a parameter and is for all intents and purposes empty for now. Over the last few days I've found some excellent write-ups on EFC and I'm currently exploring a CustomConfigurationProvider as a possible route, but I must admit I'm a little lost on starting out in ASP.Net Core.
Is it possible to hook into the freshly added DbContext within the ConfigureServices method? Or can/must I add this database to the service collection at a later point?
Thanks!
Edit 1:
I just found this post, which mentions that a DbContext cannot be used within OnConfiguring as it's still being configured; which makes a lot of sense. I'm now wondering if I can push all three DbContexts into a custom middleware to encapsulate, configure and make the connections available; something new to research.
Edit 2:
I've found another post, describing how to "Inject DbContext when database name is only know when the controller action is called" which looks like a promising starting point; however this is for an older version of ASP.Net Core, according to https://learn.microsoft.com "DbContextFactory" has been renamed so I'm now working to update the example given into a possible solution.
So, I've finally worked it all out. I gave up on the factory idea as I'm not comfortable enough with asp.net-core-2.0 to spend time working it out & I'm rushing head-long into a deadline so the faster options are now the better ones and I can always find time to refactor the code later (lol).
My appsettings.json file currently just contains the following (the relevant bit of appsettings.Developments.json is identical):
{
"ConnectionStrings" : {
"Accounts": "Server=testserver;Database=Accounts;Trusted_Connection=True;",
"Client": "Server=testserver;Database={CLIENT_DB};Trusted_Connection=True;",
"Configuration": "Server=testserver;Database=Configuration;Trusted_Connection=True;"
},
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
I've opted to configure the two static databases in the ConfigureServices method of StartUp, these should be configured and ready to use by the time the application gets around to having to do anything. The code there is nice & clean.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<MvcOptions>(options =>
{
//options.Filters.Add(new RequireHttpsAttribute());
});
services.AddDbContext<AccountsContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Accounts"))
);
services.AddDbContext<ConfigContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Configuration"))
);
services.AddSingleton(
Configuration.GetSection("ConnectionStrings").Get<ConnectionStrings>()
);
}
It turns out that one can be spoilt for choice in how to go about accessing configuration options set in the appsettings.json, I'm currently trying to work out how I've managed to get it to switch to the release version instead of the development one. I can't think what I've done to toggle that...
To get the placeholder config setting I'm using a singleton to hold the string value. This is just dipping into the "ConnectionStrings" group and stuffing that Json into the "ClientConnection" object (detailed below).
services.AddSingleton(
Configuration.GetSection("ConnectionStrings").Get<ClientConnection>()
);
Which populates the following structure (that I've just bunged off in its own file):
[DataContract(Name = "ConnectionStrings")]
public class ClientConnection
{
[DataMember]
public string Client { get; set; }
}
I only want this holding the connection string for the dynamically assigned database, so it's not too jazzy. The "Client" DataMember is what is selecting the correct key in the Json, if I wanted a different named node in the Json I'd rename it to "Accounts", for instance.
Another couple of options I tested, before settling on the Singleton option, are:
services.Configure<ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));
and
var derp = Configuration.GetSection("ConnectionStrings:Client");
Which I discounted, but it's worth knowing other options (they'll probably be useful for loading other configuration options later).
I'm not keen on the way the Controller dependencies work in ASP.Net Core 2, I was hoping I'd be able to hide them in a BaseController so they wouldn't have to be specified in every single Controller I knock out, but I've not found a way to do this yes. The dependencies needed in the Controllers are passed in the constructor, these weirded me out for a while because they're auto-magically injected.
My BaseController is set up as follows:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore.Internal;
using ServiceLayer.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ServiceLayer.Controllers
{
public class BaseController : Controller
{
private readonly ClientConnection connectionStrings;
private readonly AccountsContext accountsContext;
private readonly ConfigurationContext configContext;
public ClientTemplateContext clientContext;
private DbContextServices DbContextServices { get; set; }
public BaseController(AccountsContext accounts, ConfigContext config, ClientConnection connection) : base()
{
accountsContext = accounts;
configContext = config;
connectionStrings = connection;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
}
}
}
The code for selecting the database then goes in the "OnActionExecuting()" method; this proved to be a bit of a pain as well, trying to ensure that the dbcontext was set up properly, in the end I settled on:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ServiceLayer.Controllers
{
public class BaseController : Controller
{
private readonly ClientConnection connectionStrings;
private readonly AccountsContext accountsContext;
private readonly ConfigurationContext configContext;
public ClientTemplateContext clientContext;
private DbContextServices DbContextServices { get; set; }
public BaseController(AccountsContext accounts, ConfigurationContext config, ClientConnection connection) : base()
{
accountsContext = accounts;
configContext= config;
connectionStrings = connection;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// Temporary selection identifier for the company
Guid cack = Guid.Parse("827F79C5-821B-4819-ABB8-819CBD76372F");
var dataSource = (from c in configContext.Clients
where c.Cack == cack
join ds in configContext.DataStorage on c.CompanyId equals ds.CompanyId
select ds.Name).FirstOrDefault();
// Proto-connection string
var cs = connectionStrings.Client;
if (!string.IsNullOrEmpty(cs) && !string.IsNullOrEmpty(dataSource))
{
// Populated ConnectionString
cs = cs.Replace("{CLIENT_DB}", dataSource);
clientContext = new ClientTemplateContext().Initialise(cs);
}
base.OnActionExecuting(context);
}
}
}
new ClientTemplateContext().Initialise() is a bit messy but I'll clean it up when I refactor everything else. "ClientTemplateContext" is the entity-framework-core generated class that ties together all the entities it generated, I've added the following code to that class (I did try putting it in a separate file but couldn't get that working, so it's staying in there for the moment)...
public ClientTemplateContext() {}
private ClientTemplateContext(DbContextOptions options) : base(options) {}
public ClientTemplateContext Initialise(string connectionString)
{
return new ClientTemplateContext().CreateDbContext(new[] { connectionString });
}
public ClientTemplateContext CreateDbContext(string[] args)
{
if (args == null && !args.Any())
{
//Log error.
return null;
}
var optionsBuilder = new DbContextOptionsBuilder<ClientTemplateContext>();
optionsBuilder.UseSqlServer(args[0]);
return new ClientTemplateContext(optionsBuilder.Options);
}
I also included using Microsoft.EntityFrameworkCore.Design; and added the IDesignTimeDbContextFactory<ClientTemplateContext> interface to the class. So it looks like this:
public partial class ClientTemplateContext : DbContext, IDesignTimeDbContextFactory<ClientTemplateContext>
This is where the CreateDbContext(string[] args) comes from & it allows us to create a new instance of a derived context at design-time.
Finally, the code for my test controller is as follows:
using Microsoft.AspNetCore.Mvc;
using ServiceLayer.Entities;
using System.Collections.Generic;
using System.Linq;
namespace ServiceLayer.Controllers
{
[Route("api/[controller]")]
public class ValuesController : BaseController
{
public ValuesController(
AccountsContext accounts,
ConfigurationContext config,
ClientConnection connection
) : base(accounts, config, connection) {}
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
var herp = (from c in clientContext.Usage
select c).FirstOrDefault();
return new string[] {
herp.TimeStamp.ToString(),
herp.Request,
herp.Payload
};
}
}
}
This successfully yields data from the database dynamically selected from the DataSource table within the Configuration database!
["01/01/2017 00:00:00","derp","derp"]
If anyone can suggest improvements to my solution I'd love to see them, my solution is mashed together as it stands & I want to refactor it as soon as I feel I'm competent enough to do so.

Can I inject dependency into migration (using EF-Core code-first migrations)?

I tried to inject IConfiguration into the migration (in constructor), and got exception: "No parameterless constructor defined for this object."
any workaround?
you cannot, the migrations need to be able to run outside the context of your application.
Since the Entity-framework command-line tool analyzes your code but does not run the startup.cs class.
Also it is not advisable. your migrations should be plain simple and not depend on anything. if it would, it could lead to major runtime side-effects where missing config could lead to missing tables or columns in production.
additional advise
If it involves a lot of small/equal/manual changes. Best way is to generate your migration file. Why? This way your migration will be deterministic: you know what the outcome will be. If a line in your migration fails, it is simple and clear why that is and easily(er) fixable.
There's a way to do what you want to do. In my scenario, I would like to use the database name in the connection string through the DbContext. EF core 2.1.1 is used. The code is modified from here
Create a custom MigrationsAssembly service
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Internal;
using System;
using System.Reflection;
public class ContextAwareMigrationsAssembly : MigrationsAssembly
{
private readonly DbContext context;
public ContextAwareMigrationsAssembly(
ICurrentDbContext currentContext,
IDbContextOptions options,
IMigrationsIdGenerator idGenerator,
IDiagnosticsLogger<DbLoggerCategory.Migrations> logger) : base(currentContext, options, idGenerator, logger)
{
context = currentContext.Context;
}
/// <summary>
/// Modified from http://weblogs.thinktecture.com/pawel/2018/06/entity-framework-core-changing-db-migration-schema-at-runtime.html
/// </summary>
/// <param name="migrationClass"></param>
/// <param name="activeProvider"></param>
/// <returns></returns>
public override Migration CreateMigration(TypeInfo migrationClass, string activeProvider)
{
var hasCtorWithDbContext = migrationClass
.GetConstructor(new[] { typeof(DbContext) }) != null;
if (hasCtorWithDbContext)
{
var instance = (Migration)Activator.CreateInstance(migrationClass.AsType(), context);
instance.ActiveProvider = activeProvider;
return instance;
}
return base.CreateMigration(migrationClass, activeProvider);
}
}
Replace the IMigrationAssembly service in your DbContext with your custom class
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.ReplaceService<IMigrationsAssembly, ContextAwareMigrationsAssembly>();
}
Then you can add a DbContext parameter in your migration.
public Migration20180801(DbContext context)
{
DatabaseName = context.Database.GetDbConnection().Database;
}
In your case, you can replace all the DbContext references with IConfiguration and the relevant instance in the CreateMigration override.
If it is just about your connection-string (is it?), you may want to check this answer, which basically suggests this code in your startup-project (not in your migrations-project):
var myConnectionString = Configuration.GetConnectionString(myConnectionStringName);
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(
myConnectionString ,
x => x.MigrationsAssembly(myDbContextAssemblyName)));

What is the Best Practice to configure StackExchange.Redis with Autofac?

In the StackExchange.Redis docs it is recommended to only create one and reuse the connection to Redis.
Azure Redis best practices recommends using the following pattern:
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
return ConnectionMultiplexer.Connect("cachename.redis.cache.windows.net,ssl=true,abortConnect=false,password=password");
});
public static ConnectionMultiplexer Connection
{
get
{
return lazyConnection.Value;
}
}
but how should I get this working with Autofac where I want the configuration to be set in the web/app config files?
I currently have a RedisCacheProvider:
private readonly ConnectionMultiplexer _connection;
public RedisCacheProvider(string config)
{
_connection = ConnectionMultiplexer.Connect(config);
}
and in my Autofac config:
builder.RegisterType<RedisCacheProvider>().As<ICacheProvider>().WithParameter("config", "localhost");
My thinking is, I should change my RedisCacheProvider to take in a ConnectionMultiplexer which is passed in via the static variable?
Update: My Solution so far:
My RedisCacheProvider (injecting an interface here allows me to mock the connection in unit tests):
private readonly IConnectionMultiplexer _connection;
public RedisCacheProvider(IConnectionMultiplexer connection)
{
_connection = connection;
}
RedisConnection class to hold the static property and read config from config file:
public class RedisConnection
{
private static readonly Lazy<ConnectionMultiplexer> LazyConnection =
new Lazy<ConnectionMultiplexer>(
() => ConnectionMultiplexer.Connect(ConfigurationManager.AppSettings["RedisCache"]));
public static ConnectionMultiplexer Connection
{
get
{
return LazyConnection.Value;
}
}
}
Registration in an Autofac Module:
builder.RegisterType<RedisCacheProvider>().As<ICacheProvider>()
.WithParameter(new TypedParameter(
typeof(IConnectionMultiplexer),
RedisConnection.Connection));
Autofac supports Implicit Relationship Types and Lazy<> evaluation is supported out of the box.
So after you register your RedisCacheProvider as in your example, that is
builder
.RegisterType<RedisCacheProvider>()
.As<ICacheProvider>()
.WithParameter("config", "localhost");
you can resolve it like below:
container.Resolve<Lazy<ICacheProvider>>();
But do not forget that default Autofac lifetime scope is InstancePerDependency(transient). That is, you will get new instance of RedisCacheProvider everytime you resolve it or whenever it is provided to other component as dependency. To fix this you need to specify its lifetime scope explicitly. For instance, to make it singleton you need to change registration as below:
builder
.RegisterType<RedisCacheProvider>()
.As<ICacheProvider>()
.WithParameter("config", "localhost")
.SingleInstance();
Another assumption here is that RedisCacheProvider is the only component where Redis connection is used. If it is not the case then you should better let Autofac manage Redis connection's life scope (which is a better idea anyway) and get the connection as a dependency in RedisCacheProvider. That is:
public RedisCacheProvider(IConnectionMultiplexer connection)
{
this.connection = connection;
}
....
builder
.Register(cx => ConnectionMultiplexer.Connect("localhost"))
.As<IConnectionMultiplexer>()
.SingleInstance();
builder
.RegisterType<RedisCacheProvider>()
.As<ICacheProvider>();