Proper way to get a DbContext in a gRPC service implementation - c#

Various examples online show injecting a DbContext into a gRPC service in the service constructor:
public ImageStorageService(ILogger<ImageStorageService> logger, SqliteDbContext dbContext)
{
_logger = logger;
_dbContext = dbContext;
}
and using that _dbContext in all the gRPC implementation methods. This doesn't seem right - I'm new to EF and .Net Core web in general but a DbContext is not thread-safe and multiple gRPC methods can be executing at the same time, yes?
Anyway I found another way, here's the constructor and an example api method implementation:
public ImageStorageService(ILogger<ImageStorageService> logger, IServiceScopeFactory serviceScopeFactory)
{
_logger = logger;
_serviceScopeFactory = serviceScopeFactory;
}
public override async Task<GetImageCountReply> GetImageCount(GetImageCountRequest request, ServerCallContext context)
{
using var scope = _serviceScopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
var count = await dbContext.StoredImages.CountAsync(context.CancellationToken);
return new GetImageCountReply { Count = count };
}
Am I over-complicating things? services.AddDbContext<> injects database contexts with Scoped lifetime so this is necessary, isn't it?

Related

How to instantiate DbContext from IHostingService

I have a class that derives from BackgroundService (IHostedService) for running background tasks. This will be added to my services using builder.Services.AddHostedService<BackgroundTaskService>()
BackgroundService's task runs for the entire duration of the web application, checking for queued data to process.
My question is, how do I instantiate an instance of DbContext from this code?
I could have the BackgroundTaskService constructor accept a DbContext. But wouldn't that keep the DbContext open forever?
And how else could I instantiate it without duplicating all the code to scan my settings file for the connection string, etc.?
The recemmended approach is to inject IDbContextFactory<TContext> as described in the following article: Using a DbContext factory (e.g. for Blazor)
Some application types (e.g. ASP.NET Core Blazor) use dependency injection but do not create a service scope that aligns with the desired DbContext lifetime. Even where such an alignment does exist, the application may need to perform multiple units-of-work within this scope. For example, multiple units-of-work within a single HTTP request.
In these cases, AddDbContextFactory can be used to register a factory for creation of DbContext instances.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ApplicationDbContext>(
options =>
options.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Test"));
}
Then in your controller:
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
You can use scope service factory. Check here for reference.
Here you have an example:
// Injection
public class DataApi : BackgroundService
{
private readonly ILogger<DataApi> logger;
private readonly IServiceScopeFactory scopeFactory;
public DataApi(ILogger<DataApi> _logger, IConfiguration _cfg, IServiceScopeFactory _sSF)
{
logger = _logger;
scopeFactory = _sSF;
// e.g. data from appsettings.json
// var recovery = _cfg["Api:Recovery"];
}
// ...
// Usage
protected async Task DataCollector()
{
logger.LogInformation("Collector");
using (var scope = scopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<MyDbContext>();
var myList = await db.MyEntity
.AsNoTracking()
.Where(t => t.active)
.ToListAsync();
if (myList.Count == 0)
{
logger.LogInformation("Empty...");
return;
}
// logic...
}
await Task.CompletedTask;
}

I am facing issue thread related issue in asp.net core identity in asp.net core?

Error Message :A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext
public async Task<UserSearchDto> GetSingle(string userId, string baseUrl)
{
var user =await _userManager.FindByIdAsync(userId);
if (user != null)
{
UserSearchDto userSearches = new UserSearchDto
{
data
};
return userSearches;
}
}
In above service FindByIdAsync throwing this exeption
while i am debugging step by step then i am not facing this error
my setup in startup file as below
services.AddTransient<IAuthService, AuthService>();
Even i changed above service method but its not working
why it requires more time to perform or there is any another issue?
Edit
these manager are passed in service
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<ApplicationRole> _roleManager;
this is ctor
public AuthService(UserManager<ApplicationUser> _userManager,
RoleManager<ApplicationRole> _roleManager,
IConfiguration configuration) : base(configuration)
{
this._userManager = _userManager;
this._roleManager = _roleManager;
}
User manage and role manager are used from Microsoft.AspNetCore.Identity
services.AddDbContext<Db>(options =>
{
options.UseSqlServer(mySqlConnectionStr);
}
);
The DbContext has a scoped service lifetime, coupled to an asp.net request. Thus services using the context should preferably also have a scoped service lifetime.
I can recommend you such approach (TModel can be yours UserSearchDto):
// Or your db context directly in class but this is better
private readonly IServiceScopeFactory _factory;
public async Task<TModel> FindByIdAsync(ulong id)
{
using var scope = _factory.CreateScope();
// your context gets here
await using var userManager = scope.ServiceProvider.GetRequiredService<UserManagerContext>();
// this is important
var entities = userManager.Set<TModel>().AsNoTracking();
// data should be filled after FindByIdAsync(ulong id), not in this method
return await entities.FirstOrDefaultAsync(t => t.Id == id);
}

DI and Classes without DI how to instantiate the DB

I have a razor pages with some classes such as scheduled tasks that run in the background. I have a IUnitofWork for the databases and uses EF.
In my schedule class "WorkerService : BackgroundService" it does routine backups and other tasks.
How can I reference the Database because I dont have DI due to not implementing razor pages?
Usually this is how I do it using DI on razor code files:
private readonly IUnitOfWork _unitOfWork;
public IndexModel(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
I am new to this DI and been in the udemy and microsoft site daily. I think I have to create a IUnit of work and pass in the ApplicationDbContext maybe in an ovveride? But how to get the context without DI.
Program.cs
builder.Services.AddHostedService<WorkerService>(); //Uses cronos to execute DoWork() every hour
WorkerService.cs
private const string schedule = "*/5 * * * *"; // every 5 for testing
private readonly CronExpression _cron;
public WorkerService()
{
_cron = CronExpression.Parse(schedule);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var utcNow = DateTime.UtcNow;
var nextUtc = _cron.GetNextOccurrence(utcNow);
await Task.Delay(nextUtc.Value - utcNow, stoppingToken);
await DoBackupAsync();
}
}
private static Task DoBackupAsync()
{
DoWork d = new DoWork();
return Task.FromResult("Done");
}
RazorApp/Pages
This is where I need to save data
RazorApp/ScheduledTasks/DoWork.cs
RazorApp/ScheduledTasks/WorkerService.cs
Attempting to DI either the IUnitOfWork or ApplicationDbContext
Further trying different examples like: https://dotnetcorecentral.com/blog/background-tasks
Results in this error as well: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: WebRazor.ScheduledTasks.BackgroundPrinter': Cannot consume scoped service 'WebRazor.DataAccess.ApplicationDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService
public BackgroundPrinter(ILogger<BackgroundPrinter> logger, IWorker worker, ApplicationDbContext dbContext)
{
this.logger = logger;
applicationDbContext = dbContext;
}
Is this where I need to get it from the settings directly or is there a slick way to grab the Db Context?
Ok, wow I was all over the place. I implemented this and it worked.
public BackgroundPrinter(ILogger<BackgroundPrinter> logger, IWorker worker, IServiceProvider serviceProvider)
{
this.logger = logger;
unitOfWork = serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IUnitOfWork>();
}
On construct pass in the Iserviceprovider and access this way. DI to the DoWork and its writing to the database.
Still not sure this is the best way to solve this problem, but I think I have to refactor my unitofwork because every now and then I get an error on writing
"A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext" but this may be easy to solve.
The background tasks with hosted services doc describes how to consume a scoped service in a background task:
Inject IServiceProvider in hosted service's ctor
Use the IServiceProvider to create scope during the execution
Use the scope to resolve required services
Personally for "timed" services like provided WorkerService in most cases I found useful to create scope per every iteration (especially if it uses EF internally). Something along this lines:
private readonly IServiceProvider _serviceProvider;
public WorkerService(IServiceProvider serviceProvider)
{
...
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var utcNow = DateTime.UtcNow;
var nextUtc = _cron.GetNextOccurrence(utcNow);
await Task.Delay(nextUtc.Value - utcNow, stoppingToken);
await DoBackupAsync(stoppingToken);
}
}
private static async Task DoBackupAsync(CancellationToken stoppingToken)
{
using (var scope = _serviceProvider.CreateScope()) // do not forget to dispose the scope
{
var backupService = scope.ServiceProvider.GetRequiredService<IBackupService>();
await backupService.BackUp(stoppingToken);
}
}

C# Dependency Injection Cannot Consume Scoped?

I have not had much experience using Dependency Injection but I am trying to use it in one of my projects. When I try to add a second parameter to the constructor of my I get an error "Cannot consume scoped service 'CarbonService' from singleton..." Not sure why I get this error or what I am missing. Thanks for your help
I would like my timer class to have access to the CarbonService object.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<CarbonDBContext>(o => o.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddHostedService<ValidUntilTimerService>();
services.AddScoped<ICarbonService, CarbonService>(); // I HAVE TRIED USING SINGLETON BUT SAME ERROR
services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(dispose: true));
}
ValidUntilTimerService.cs:
// ADDING THE SECOND PARAMETER TO THIS CONSTRUCTOR CAUSES THE ERROR.
public class ValidUntilTimerService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
private ICarbonService _carbonService;
public ValidUntilTimerService(ILogger<ValidUntilTimerService> logger, ICarbonService carbonService)
{
_logger = logger;
_carbonService = carbonService;
}
...
}
CarbonService.cs:
public interface ICarbonService
{
WattTime ReadCarbon();
void SaveCarbon(int carbonIndex);
}
public class CarbonService : ICarbonService
{
private IConfiguration _configuration;
private readonly CarbonDBContext _dbcontext;
public CarbonService(IConfiguration configuration, CarbonDBContext dbContext)
{
_configuration = configuration;
_dbcontext = dbContext;
}
public WattTime ReadCarbon()
{
var wt = new WattTime();
...
return wt;
}
public void SaveCarbon(int carbonIndex)
{
...
}
}
IHostedServices are registered as singletons. You are injecting ICarbonService into ValidUntilTimerService, which means you are injecting a scoped service into a singleton service.
If we consider the lifetimes of these two types of services:
Scoped services are created once per request. This means, when a scoped service is instantiated, it stays alive until the end of the request.
When singleton service gets instantiated, it stays alive until the app shuts down.
we realize that it doesn't make much sense to consume scoped service from a singleton service. This is what would happen in that situation:
When singleton service (ValidUntilTimerService in your case) gets instantiated, its scoped dependency (ICarbonService in your case) also gets instantiated and injected to the singleton service. When the request is completed, the singleton service stays alive, but the scoped service gets disposed. Now your singleton service ended up with a "dead" dependency (_carbonService field in your case).
The above scenario is not possible. It is just a "what if" scenario. The point is, you cannot consume a service that is created per request from a service that can be created without request (e.g. upon app startup).
Now, that explains the cause of your issue, but let's see what you can do to solve it.
POSSIBLE SOLUTION
You can use IServiceScopeFactory to create your own scope inside of the ValidUntilTimerService:
public class ValidUntilTimerService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
private IServiceScopeFactory _serviceScopeFactory;
public ValidUntilTimerService(ILogger<ValidUntilTimerService> logger, IServiceScopeFactory serviceScopeFactory)
{
_logger = logger;
_serviceScopeFactory = serviceScopeFactory;
}
// ...
using (var scope = _serviceScopeFactory.CreateScope())
{
ICarbonService carbonService = scope.ServiceProvider.GetService(typeof(ICarbonService));
// ...
}
// ...
}

Dependency injection of type having nested dependency

I want to create a type which contains another dependency.
The reason is so that I can make the controller rely on a Infrastructure wrapper with more abstracted functions which then actually access the data layer.
So here is the wrapper having dependency of dbcontext
public Repository(IdbContext context)
{
_context = context;
}
The controller constructor:
private readonly Repository _repo;
public TheController(IRepository repo)
{
_repo = repo;
}
In the Startup.cs, ConfigureServices:
services.AddDbContext<dbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("dbContext")));
services.AddTransient<IRepository, Repository>();
In the Program.cs,
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<dbContext>();
var _repo = services.GetRequiredService<IRepository>();
...
}
The GetRequiredService<IRepository>() fails with following Exception:
System.InvalidOperationException: 'Unable to resolve service for type
'namespace.Models.IdbContext' while attempting to activate
'namespace.Repositories.Repository'.'
Associate the IdbContext interface with the dbContext implementation.
Assuming
public class dbContext: DbContext, IdbContext {
//...
}
It is failing because it was not registered and the provider does not know what to initialized when injecting IdbContext into the repository.
services.AddDbContext<IdbContext, dbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("dbContext")));
services.AddTransient<IRepository, Repository>();
You need to inject the same type that you register.
If you register a dbContext, then inject a dbContext.
services.AddDbContext<dbContext>(options => ...);
public Repository(dbContext context)
{
_context = context;
}
If you register an IdbContext, then inject an IdbContext.
services.AddDbContext<IdbContext, dbContext>(options => ...);
public Repository(IdbContext context)
{
_context = context;
}
Here is the latter option as a runnable demo:
https://github.com/shaunluttin/asp-net-core-db-context-interface-injection

Categories

Resources