I'm using .Net Core Background Service to Connect to Kafka and save messages to SQL Server. My Project Structure looks like this:
In the Infrastructure Dependency, I have the following code to register Entity Framework using IConfiguration configuration passed from Worker's Program.cs file i.e. services.AddInfrastructure(configuration);
namespace JS.Svf.BackgroundServices.Infrastructure
{
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
// Add all the dependencies required by Azure Functions.
// From Infrastructure
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
services.AddTransient<IApplicationDbContext>(provider => provider.GetRequiredService<ApplicationDbContext>());
services.AddTransient<IProductRepository, ProductRepository>();
services.AddTransient<ISupplierRepository, SupplierRepository>();
return services;
}
}
}
I'm getting the following error after running the Background Service:
Cannot consume scoped service 'ApplicationDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService'
With reference, I came to know that we need to use IServiceScopeFactory but I'm bit clueless about how to use it with the current structure. Please advice.
The repository uses the ApplicationDbContext. How to use IServiceScopeFactory here?
namespace JS.Svf.Functions.Infrastructure.Persistence
{
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly ApplicationDbContext _context;
public Repository(ApplicationDbContext context)
{
_context = context;
}
public void Add(TEntity entity)
{
_context.Set<TEntity>().Add(entity);
_context.SaveChanges();
}
}
}
In your singleton service, the IHostedService, inject an IServiceScopeFactory into it and use that to create a scope and get a new DbContext from it. For example:
public class MyHostedService : IHostedService
{
private readonly IServiceScopeFactory _scopeFactory;
public MyHostedService(IServiceScopeFactory scopeFactory)
{
// Inject the scope factory
_scopeFactory = scopeFactory;
}
private async Task SomeMethodThatNeedsTheContext()
{
// Create a new scope (since DbContext is scoped by default)
using var scope = _scopeFactory.CreateScope();
// Get a Dbcontext from the scope
var context = scope.ServiceProvider
.GetRequiredService<ApplicationDbContext>();
// Run a query on your context
var quadrupeds = await context.Animals
.Where(a => a.Legs == 4)
.ToListAsync();
}
}
Related
I have an ASP.NET Core 3.1 Web API project with Microsoft.EntityFrameworkCore version 2.2.6 and Azure SQL as the data source for the application.
I have the following code for setting up the DbContext using a dependency injection Container in the Startup.cs
public static IServiceCollection AddAppDBConfiguration(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment webHostEnvironment) => services.AddEntityFrameworkSqlServer().AddDbContext<AppDbContext>(appDbContext =>
{
var appDataDBconn = configuration[VaultKeys.DataDBConnString];
if (webHostEnvironment.IsEnvironment(Constants.Local))
{
appDataDBconn = configuration.GetSection(nameof(ApplicationOptions.Local)).Get<Local>().LocalDataDBConn;
}
appDbContext.UseSqlServer(appDataDBconn, sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
});
The repository class used in this case is also marked as scoped lifetime in this case.
There is no usage of async and await in the method defined at repository layer level.
[HttpGet("{input1}", Name = "TestMethod")]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public dynamic TestMethod(string input1) => _testRepo.UpdateData(input1);
public class TestRepo : ITestRepo
{
private static AppDbContext _appDbContext;
public TestRepo(AppDbContext appDbcontext)
{
_appDbContext = appDbcontext;
}
public dynamic UpdateData(string input1) => _appDbContext.TestTable.Where(e => e.TestName == input1).Select(e => e.TestId).FirstOrDefault();
}
In the Azure AppInsights, I came across the below error
Error 500: System.InvalidOperationException for “GET TestController/TestMethod”.
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, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations.
Can anyone help me to resolve this issue?
The context should not be static.
From DbContext Lifetime, Configuration, and Initialization:
public class MyController
{
private readonly ApplicationDbContext _context;
public MyController(ApplicationDbContext context)
{
_context = context;
}
}
I have created a custom logger that logs to database. The issue I am facing is that when I run my migration, there is a conflict between the AppDbContext and MyLoggerProvider service. It seems that the issue is caused by the fact that the MyLoggerProvider is a singleton service, while the AppDbContext service is a scoped service.
How can I fix this issue to be able to run my migration successfully?
Program.cs:
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlite(builder.Configuration.GetConnectionString("AppDbConnection"));
});
builder.Services.AddLogging();
builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
MyLoggerProvider.cs:
public class MyLoggerProvider : ILoggerProvider
{
private readonly AppDbContext dbContext;
private readonly LogLevel minLevel = LogLevel.Information;
public MyLoggerProvider(AppDbContext dbContext)
{
this.dbContext = dbContext;
}
public ILogger CreateLogger(string categoryName)
{
return new MyLogger(minLevel, dbContext);
}
// rest of the code
}
MyLogger.cs:
public class MyLogger : ILogger
{
private readonly AppDbContext dbContext;
private readonly LogLevel minLevel;
public MyLogger(LogLevel minLevel, AppDbContext dbContext)
{
this.minLevel = minLevel;
this.dbContext = dbContext;
}
// rest of the code
}
Update:
I used IServiceScopeFactory to access the DbContext service as shown in the updated code below:
public class MyLoggerProvider : ILoggerProvider
{
private readonly LogLevel minLevel = LogLevel.Information;
private readonly IServiceScopeFactory scopeFactory;
public MyLoggerProvider(IServiceScopeFactory scopeFactory)
{
this.scopeFactory = scopeFactory;
}
public ILogger CreateLogger(string categoryName)
{
using (var scope = scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return new MyLogger(minLevel, dbContext);
}
}
public void Dispose(){}
}
I thought this would work, but it times out when creating the migration.
An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: Timed out waiting for the entry point to build the IHost after 00:05:00. This timeout can be modified using the 'DOTNET_HOST_FACTORY_RESOLVER_DEFAULT_TIMEOUT_IN_SECONDS' environment variable.
Unable to create an object of type 'AppDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
Either modify your logger service to be Scoped, or setup your db context to be transient:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Transient,
ServiceLifetime.Transient);
Or see more solutions here: Use DbContext in ASP .Net Singleton Injected Class
I am trying to create a class that seeds user and role data.
My class that seeds data takes a RoleManager constructor parameter
public class IdentityDataSeeder
{
private readonly RoleManager<IdentityRole> roleManager;
public IdentityDataSeeder(RoleManager<IdentityRole> roleManager)
{
this.roleManager = roleManager;
}
public async Task SeedData()
{
// Do stuff with roleManager
}
}
I call it from Main like this
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var dataSeeder = scope.ServiceProvider.GetService<IdentityDataSeeder>();
await dataSeeder.SeedData();
}
host.Run();
}
I configure my dependenies like this (note that I have two DbContexts in my application; one context has my Identity tables, and the other context has my application tables).
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyIdentityDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultAdminConnection")));
services.AddDbContext<MyApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IdentityDataSeeder, IdentityDataSeeder>();
services.AddScoped<IRoleStore<IdentityRole>, RoleStore<IdentityRole>>();
services.AddScoped<RoleManager<IdentityRole>, RoleManager<IdentityRole>>();
}
My two context classes look like this
public class MyIdentityDbContext : IdentityDbContext
{
// ...
}
public class MyApplicationDbContext : DbContext
{
// ...
}
When I run the program, I get the error
InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.RoleStore1[Microsoft.AspNetCore.Identity.IdentityRole]'.
I assume the problem is that since I have two DbContexts registered in my dependencies, the service provider can't figure out which one to use when it tries to resolve RoleStore (correct me if I'm wrong).
How do I tell the service provider to inject the MyIdentityDbContext dependency into RoleStore?
You will need to use the factory delegate with the ActivatorUtilities Class that uses the specific DbContext resolved via the provider.
//...
services.AddScoped<IRoleStore<IdentityRole>>( sp => {
DbContext context = sp.GetService<MyIdentityDbContext>();
return ActivatorUtilities.CreateInstance<RoleStore<IdentityRole>>(sp, context);
});
//...
When resolving the role store, the service provider will use the provided DbContext and resolve the remaining dependencies to be injected into the instance.
Bit of a newbie question. I am having trouble getting access to dependency injected services from within my own custom class in ASP.NET Core 3.1
I can access services fine from within a controller or razor page e.g. I can get hold of configuration and data context information:
public class DetailModel : PageModel
{
private readonly MyDataContext _context;
private readonly IConfiguration _config;
public DetailModel(MyDataContext context, IConfiguration config)
{
_context = context;
_config = config;
}
etc......
}
I now wish to access these from the constructor of a custom class that is not a controller or razor page. e.g. I am using:
public class ErrorHandling
{
private readonly MyDataContext _context;
private readonly IConfiguration _config;
public ErrorHandling(MyDataContext context, IConfiguration config)
{
_context = context;
_config = config;
}
}
The problem is that when I instantiate my class it insists on me passing the service values into the constructor:
var myErrorHandler = new ErrorHandling(`<wants me to pass context and config values here>`)
This defeats the whole point of DI. I think I am missing something fundamental here!
What am I missing?
You can register ErrorHandling as a service too, in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// other stuff..
services.AddScoped<ErrorHandling>(); // this should work as long as both 'MyDataContext' and 'IConfiguration' are also registered
}
If you need an instance of ErrorHandling in your page model, you can specify it in the constructor and ASP.NET Core will resolve it for you at runtime.
This way you won't have to new it:
public class DetailModel : PageModel
{
private readonly MyDataContext _context;
private readonly IConfiguration _config;
private readonly ErrorHandling _errorHandling;
public DetailModel(ErrorHandling errorHandling, MyDataContext context, IConfiguration config)
{
_context = context;
_config = config;
_errorHandling = errorHandling;
}
}
This article can be useful: Dependency injection in ASP.NET Core
If you don't want register as a service, you can use ActivatorUtilities.CreateInstance to resolve ErrorHandling.
Instantiate a type with constructor arguments provided directly and/or from an IServiceProvider.
e.g.:
// IServiceProvider serviceProvider = ...;
var errorHandling = ActivatorUtilities.CreateInstance<ErrorHandling>(serviceProvider);
BUT you need to be careful about this solution:
ServiceProvider scope should equal with dependency object (MyDataContext, IConfiguration). Otherwise, you will get an exception like:
var errorHandling = ActivatorUtilities.CreateInstance<ErrorHandling>(app.ApplicationServices);
// An exception of type 'System.InvalidOperationException' occurred
// in Microsoft.Extensions.DependencyInjection.dll but was not handled in user cod
// e: 'Cannot resolve scoped service 'three.MyDataContext' from root provider.'
For this, you can create an scope to resolve ErrorHandling:
using (var scope = app.ApplicationServices.CreateScope())
{
var errorHandling = ActivatorUtilities.CreateInstance<ErrorHandling>(scope.ServiceProvider);
}
Dependency injection service would not call Dispose on IDisposable instances even out of scope.
For this, you should call Dispose() by yourself:
using (var scope = app.ApplicationServices.CreateScope())
{
using var disposablClass = ActivatorUtilities.CreateInstance<DisposablClass>(scope.ServiceProvider);
}
ActivatorUtilities.CreateInstance will new an instance even you use the same ServiceProvider:
using (var scope = app.ApplicationServices.CreateScope())
{
var errorHandling1 = ActivatorUtilities.CreateInstance<ErrorHandling>(scope.ServiceProvider);
Console.WriteLine(errorHandling1.GetHashCode());
// 11903911
var errorHandling2 = ActivatorUtilities.CreateInstance<ErrorHandling>(scope.ServiceProvider);
Console.WriteLine(errorHandling2.GetHashCode());
// 40026340
}
I try get data from my database with repository Pattern
i have 3 project
Bmu.Mode 'this is for model to create database'
Bmu.Repo 'it have 2 folder for repository include contract/InewsRepository.cs' and 'Repository/NewsRepository' for implement Interface
Bmu.Api for invoke data from Repo project
news class in Model Project
namespace bmu.model
{
public class News
{
public int Id { get; set; }
public string SubTitle { get; set; }
public string Title { get; set; }
public string Summery { get; set; }
}
}
context class in model project
namespace bmu.model
{
public class BmuContext : DbContext
{
public BmuContext(DbContextOptions<BmuContext> options): base(options)
{
}
public DbSet<News> News { get; set; }
}
}
My interface in Repo project
namespace bmu.repo.Contracts
{
public interface INewsRepository
{
Task<IEnumerable<News>> GetAllAsync();
Task<IEnumerable<News>> GetAllActiveAsync();
}
}
implement interface in bmu.repo
namespace bmu.repo.IRepository
{
public class NewsRepository : INewsRepository
{
private readonly BmuContext _context;
private readonly MemoryCache _memoryCache;
public NewsRepository(BmuContext context, MemoryCache memoryCache)
{
_context = context;
_memoryCache = memoryCache;
}
public async Task<IEnumerable<News>> GetAllAsync()
{
return await _context.News.ToListAsync();
}
public async Task<IEnumerable<News>> GetAllActiveAsync()
{
return await _context.News.Where(x => x.Active).ToListAsync();
}
}
}
Also add
services.AddControllers();
services.AddSingleton<INewsRepository, NewsRepository>();
in startup of Api project
and this is my controller
namespace bmu.api.Controllers
{
[ApiController]
[Route("[controller]")]
public class NewsController : ControllerBase
{
private readonly ILogger<NewsController> _logger;
private readonly INewsRepository _newsRepository;
public NewsController(ILogger<NewsController> logger,INewsRepository newsRepository)
{
_logger = logger;
_newsRepository = newsRepository;
}
[HttpGet]
public async Task<IEnumerable<News>> Get()
{
return await _newsRepository.GetAllActiveAsync();
}
}
}
but when run project i got this error
AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: bmu.repo.Contracts.INewsRepository Lifetime: Singleton ImplementationType: bmu.repo.IRepository.NewsRepository': Unable to resolve service for type 'bmu.model.BmuContext' while attempting to activate 'bmu.repo.IRepository.NewsRepository'.)
also because of multi project add DbContext with this
UPDATE:
namespace bmu.model
{
public class BmuContextFactory : IDesignTimeDbContextFactory<BmuContext>
{
public BmuContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<BmuContext>();
optionsBuilder.UseSqlite("Data Source=bmu.db");
return new BmuContext(optionsBuilder.Options);
}
}
}
Is there any solution for this error ?
Firstly,you need to change:
services.AddSingleton<INewsRepository, NewsRepository>();
To:
services.AddTransient<INewsRepository, NewsRepository>();
Secondly,you need to inject IMemoryCache instead of MemoryCache in NewsRepository.
Here is a simple demo like below:
1.Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSession();
services.AddTransient<INewsRepository, NewsRepository>();
services.AddDbContext<BmuContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Connectionstring")));
services.AddMemoryCache();
}
2.appsettings.json:
"ConnectionStrings": {
"Connectionstring": "Server=(localdb)\\mssqllocaldb;Database=Bmu;Trusted_Connection=True;MultipleActiveResultSets=true"
}
3.NewsRepository:
public class NewsRepository : INewsRepository
{
private readonly BmuContext _context;
private readonly IMemoryCache _memoryCache;
public NewsRepository(BmuContext context, IMemoryCache memoryCache)
{
_context = context;
}
//...
}
My Error was that I was injecting the service class instead of the interface
It was
//This is wrong
Private readonly DataSerive _dataService;
public void EmployeeHandler(DataSerive dataService)
{
_dataService = dataService;
}
But it should be
//This is correct
Private readonly IDataSerive _dataService;
public void EmployeeHandler(IDataSerive dataService)
{
_dataService = dataService;
}
Here DataService is the class that handles operation
and IDataService is the interface
There is a lifetime type mismatch in your API. EntityFramework DbContext is a scoped service, and you cannot have a singleton instance of the NewsRepository, as it depends on an instance that is generated for each request.
You either have to use NewsRepository as a scoped service, or restructure your dependency resolution, like shown in this SO answer: Use DbContext in ASP .Net Singleton Injected Class
Like Sotiris Koukios-Panopoulos -san comment
I see you are only setting up the options for design time, not in your Startup.cs. I expect a:
services.AddDbContext<BmuContext>(options => options.UseSqlite("your connection string"));
instead.
In my case, I forgot to set this in my Startup.cs
services.AddDbContext<myDbContext>(o => o.UseSqlServer(myConnectionString));
and I forgot to mention this, because I'm using interface an service
services.AddScoped<IMyTruckService, MyTruckService>();
I was adding singleton service that was injecting DbContext class.
services.AddSingleton<WeatherForecastService>();
I changed above to below (Added a transient service scope) and it worked.
services.AddTransient<FoodItemService>();
This error can be caused by circular dependency.
Because probably, you inject service1 in service2 and also service2 in service1.
You should change it and break circular dependency.
I was having two dbcontext, and forgotten to mention this in startup.cs file
services.AddDbContext<Abc>(option => option.UseSqlServer(Configuration.GetConnectionString("ConStr")));
It was because of
private readonly IMemoryCache _memoryCache;
when i remove it every think work fine