Resolve DbContext by Interface in EFCore 3 - c#

What's the correct way to register/resolve a DbContext by an Interface?
Context:
I have multiple web services with a different dbcontext each.
Since I need to write some common functionalities in a shared project, I wrote an interface which is implemented by each dbcontext, so I can have the set of shared functionalities "dbcontext-implementation-agnostic" just injecting the dbcontext interface.
In each project, each dbcontext is already used and injected by its implementation, but I would like to use just its interface in the common features project.
So, in each webservice I would have something like:
services.AddDbContext<IMyDbcontext, MyDbcontext>(options =>
{
options.UseSqlServer(configuration.GetConnectionString("MyDbContext"));
});
services.AddTransient<ISharedService, SharedService>();
and in the shared project
public class SharedService : ISharedService
{
public SharedService(IMydbcontext context)
{
[...]
}
[...]
}
having IMyDbcontext just like
public interface IMyDbcontext
{
DbSet<SharedSettings> Settings { get; set; }
int SaveChanges(bool acceptAllChangesOnSuccess);
int SaveChanges();
Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default);
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
and I require ISharedService within Startup.cs - Configure() with
app.ApplicationServices.GetService<ISharedService>();
I've been googling a lot and tried different approches but I couldn't find one which worked...
I've tried to use a forwarder, like this: (as suggested here)
services.AddDbContext<MyDbContext>();
services.AddScoped<IMyDbContext>(provider => provider.GetRequiredService<MyDbContext>());
But, while I've no issues resolving the concrete MyDbContext, I get this error whenever I try to resolve IMyDbContext
System.InvalidOperationException: 'Cannot resolve
'Blah.IMyService' from root provider because it
requires scoped service
'Blah.IMydbcontext'.'
where IMyService is registered as transient and it's implementation constructor is
public MyService(IMydbcontext context)
I also tried to register the dbcontext like this
services.AddDbContext<IMyDbContext, MyDbContext>();
but then, when I try to resolve MyDbContext i get null, and I can't understand why

As pointed out by /u/AngularBeginner:
The IMyService requires a scoped IMyDbContext, but within your
startups Configure() method you don't have a scope.
You can make your IDbContext transient (if that's acceptable to
you).
Alternatively you can try to create a temporary scope using the
CreateScope() method, then get your IMyService from that new scoped
provider.
Source
So, I created a scope with IServiceScopeFactory and it did the trick
var scopeFactory = app.ApplicationServices.GetService<IServiceScopeFactory>();
using var scope = scopeFactory.CreateScope();
var myService = scope.ServiceProvider.GetService<IMyService>();

Related

Is there a way to factory inject a custom DbContext?

...Maybe using TFactory in AddDbContextFactory<TContext, TFactory> in EF Core extensions?
I've only seen AddDbContextFactory examples being used with just the TContext generic. They're always very explicit to say you have to use a using statement.
In similar situations (when I useClass in Angular or AddScoped in .NET Core), I make the variable I want to see in a constructor the first generic argument and the second generic argument what actually gets injected. You know, like:
services.AddScoped<IService, RealService>();
Obviously, this isn't the case with
services.AddDbContextFactory<ADbContextIHaveBeenInjecting, AFactoryThatWillReturnADbContextIHaveBeenInjecting>();
I was hoping this would eliminate the need to do the whole using thing.
Is there another way I can do this without having to re-write every injected DbContext to conform with their prescribed:
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
As I said, my hope was to use something like this for the factory:
public class MyDbContextFactory : IDbContextFactory<MyDbContext>
{
public MyDbContextFactory(DbContextOptions options)
{
}
public MyDbContext CreateDbContext()
{
var ProviderName = GetProviderName();
switch (ProviderName)
{
case "System.Data.SqlClient":
return new SqlServerDbContext(new DbContextOptionsBuilder<SqlServerDbContext>().UseSqlServer(ConnectionString).Options);
case "Npgsql":
return new PostgreSqlDbContext(new DbContextOptionsBuilder<PostgreSqlDbContext>().UseNpgsql(ConnectionString).Options);
default:
throw new NullReferenceException("Missing provider name for DbContext. Should be Npgsql or System.Data.SqlClient");
}
}
}
Then, set it up in Startup.cs ConfigureServices like:
services.AddDbContextFactory<MyDbContext, MyDbContextFactory>();
So I could inject in a class like this:
public class MyController : BaseApiController
{
private readonly MyDbContext _myDbContext;
public MyController(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
[HttpGet("GetACount")]
public IActionResult GetACount()
{
var count = _myDbContext.MyRecord.Count();
return Ok(count);
}
...
Is there a way to do this using the AddDbContextFactory? What is TFactory actually for? Is there another way to do this?
DbContextFactory is specifically intended to require you to manage the lifecycle of your DbContext, because Blazor server apps don't use a Scope-per-Http request like ASP.NET Core does, so a Scoped DbContext won't work.
If you want a Scoped DbContext just use .AddDbContext intead of .AddDbContextFactory.
If you have registered a DbContextFactory but still want to inject a scoped DbContext direcly in your services, then register it like this:
services.AddScoped<MyDbContext>(sp =>
{
var cf = sp.GetRequiredService<IDbContextFactory<MyDbContext>>();
var db = cf.CreateDbContext();
return db;
});
Dependency inject the concrete class. create a factory to select the subclass by type. Create a parent class with a private DbContext _dbContext. Inherit the subclass from the parent class and call the parent class constructor with :base(dbContext) of the subclass. The parent class can now access in its methods the subclass context. The subclass can share the methods of the parent class for (add, select, update, and deleting by set the data context of the subclass). the subclass will dependency inject the specific dbcontext in its constructor and set the parent class dbcontext variable in its constructor. the subclass repository class can then access the base class methods within its body.
in startup define the subclass repository pattern
in public void ConfigureServices(IServiceCollection services)
var connectionString = Configuration.GetConnectionString("ABCContext_DbCoreConnectionString");
services.AddDbContext<ABCContext>(options1 => options1.UseSqlServer(connectionString));
services.AddTransient<IRepositoryMySubclass, RepositoryMySubclass>();
sub class
public RepositorySubclass(ABCContext dbContext) : base(dbContext)
{
_dbContext = dbContext;
}

EF Core Data Context injection across Projects

I have project A which is a class library and project B that uses A. Project A is meant to be a generic helper library that can be used across projects (such as B).
The EF Core datacontext and the data entities need to be defined in project B (as they can vary by project) but I need to inject the datacontext in to constructors of service classes in Project A (which handle everything in a generic way).
In Project B I have the datacontext
public class MyDataContext : DbContext
{
public MyDataContext(DbContextOptions<MyDataContext> options): base(options)
{
}
public DbSet<Test> Tests { get; set; }
}
In project A I have class UnitOfWork that implements IUnitOfWork. In it's constructor I need to inject the datacontext. However since project A cannot reference project B (project A is meant to be generic), I cannot use the actual name of the datacontext in the parameter list. Since the datacontext inherits from DbContext, I tried
public UnitOfWork(DbContext dc){...}
In the startup of Project B, I have
services.AddDbContext<MyDataContext>(options =>
{
options.UseSqlServer("...<the connection string> ...");
});
services.AddScoped<IUnitOfWork, UnitOfWork>();
Everything compiles but in runtime when the UnitOfWork needs to be created, I get the error
System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: IUnitOfWork Lifetime: Scoped ImplementationType: UnitOfWork': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'UnitOfWork'.)
The Inner Exception is
InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'UnitOfWork'.
Any help is deeply appreciated.
EDIT
I was asked for the UnitOfWork class details in the comments. So here it is
public class UnitOfWork : IUnitOfWork
{
private readonly DbContext dc;
private readonly IServiceProvider serviceProvider;
public UnitOfWork(DbContext dc, IServiceProvider serviceProvider)
{
this.dc = dc;
this.serviceProvider = serviceProvider;
}
public void BeginTransaction()
{
dc.Database.BeginTransaction();
}
public void BeginTransaction(IsolationLevel isolationLevel)
{
dc.Database.BeginTransaction(isolationLevel);
}
public void CommitTransaction()
{
dc.Database.CommitTransaction();
}
public void RollbackTransaction()
{
dc.Database.RollbackTransaction();
}
public bool IsTransactionActive()
{
return dc.Database.CurrentTransaction != null;
}
public async Task<bool> SaveAsync()
{
return await dc.SaveChangesAsync() > 0;
}
public bool Save()
{
return dc.SaveChanges() > 0;
}
}
Your UnitOfWork service depends on a DbContext type, not the derived MyDataContext type which is registered into DI.
So you have two options:
You can modify the UnitOfWork registration like this (tell the IoC container to instantiate UnitOfWork with MyDataContext):
services.AddScoped<IUnitOfWork>(srp => new UnitOfWork(srp.GetRequiredService<MyDataContext>(), srp));
Or you can register DbContext into DI as well, so the DI container knows that when someone asks for a DbContext it should return MyDbContext:
services.AddScoped<DbContext, MyDataContext>();
Note that the ServiceProvider field seems to be unused in your UnitOfWork class.
The solution was to make two changes. First was to explicitly register the service as suggested by #fbede
services.AddScoped<DbContext, MyDataContext>();
Now when we do this, we lose the convenience of setting the DbContextOptionsBuilder options via the AddDbContext Extension method.
So we need to override the OnConfiguring method in the datacontext to set the configurations options we need. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(configuration.GetConnectionString(<key>));
}
Of course, IConfiguration is injected in the MyDataContext constructor

Create scope using IServiceProvider from singleton instance

Let's say I have my own class QueueListener<TService, TPayload> inherited from BackgroundService. It opens TCP connection and listens incoming messages. On each message I would like to initialize service of TService type and pass deserialized from JSON instance of TPayload to it. TService is going to be registered as Transient, so it means to be lightweight and stateless as a handler for payload have to be (in my current task). For this purpose I am going to inject IServiceProvider in constructor of my QueueListener and create a scope on each message it receives. Does it sounds like a plan or am I overengineering? I want to avoid TService is singleton as well.
Documentation says:
It's dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
But I am not completely sure what does it means. There is no way to inject scoped service in BackgroundService because it has Singleton lifetime. Do they warn me to stop doing things like I do?
UPD #1
I explain why I suppose to create scope on each message. The idea behind that is to prevent listener to be blocked by message processing and to provide other developers possibility to create their own handlers and do some stuff on received message. Other developers can create database connections for instance while processing and I want it to be closed and released when handling is done.
Register TService as scoped and create a new scope per message. Then resolve TService from created scope. Just read Consuming a scoped service in a background task
You can write it like this:
services.AddHostedService<MyBackgroundService>();
services.AddScoped<IScopedServicePerMessage, ScopedServicePerMessage>();
...
public class MyBackgroundService : BackgroundService
{
private readonly IServiceProvider _sp;
public MyBackgroundService(IServiceProvider sp)
{
_sp = sp;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
DoWork(stoppingToken);
return Task.CompletedTask;
}
private void DoWork(CancellationToken stoppingToken)
{
while(true)
{
var msg = GetNextMessage();
using (var scope = _sp.CreateScope())
{
var servicePerMessage = scope.ServiceProvider.GetRequiredService<IScopedServicePerMessage>();
servicePerMessage.Handle(msg);
}
}
}
...
}
Regarding this:
It's dangerous to resolve a scoped service from a singleton. It may
cause the service to have incorrect state when processing subsequent requests.
It's about the case when you inject scoped service (ef core dbcontext, for instance) directly into singleton. It's not your case.
The documentation is referring to injecting a scoped service into a singleton service. Since the injection happens at the construction of the singleton object, the scoped service would be provided at that time. This will effectively increase the lifetime of the scoped service to that of a singleton service. This is dangerous because a scoped service lifetime is often chosen explicitly to ensure that the object gets disposed quickly again.
The most common example would be a database context which owns a database connection; you want to make sure that you free up this database connection as soon as possible to free up the resources. But if you injected the context into a singleton service, it would never get disposed.
That however does not mean that there is no way to consume scoped services within a singleton service. This is done by having the singleton service create a service scope from which it can then retrieve singleton services. It’s important though that this service scope is supposed to be short-lived. So take the example from ASP.NET Core itself, where a service scope is created for every request, and do something similar. For example in your case, you could do it for every incoming message if that makes sense for your application.
To create a service scope, you should inject an IServiceScopeFactory; you can then create a scope with it like this:
public async Task Process(TPayload payload)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var service = scope.GetService<TService>();
await service.Process(payload);
}
}
This pattern is strictly only necessary if you need to consume scoped services. You could resolve all other services directly without creating a scope. If you can reuse the same service instance to process all payloads, you could also inject the service as a singleton (same as registering it as transient but resolving it only once). If you need a fresh instance for every payload, then consider creating a scope even if it isn’t strictly necessary.
First, transient services are not scoped services. Transient services are usually externally owned by your code and are created each time they are resolved from the container. Container does not cache transient services.
TService is going to be registered as Transient ... For this purpose I am going to inject IServiceProvider in constructor of my QueueListener and create a scope on each message it receives.
You do not need a scope for resolving transient services. Even if you create a scope, the scope still does not manage / own transient services. That, for example, ending the lifetime of the scope does not end lifetime of transient services.
You could simply use the IServiceProvider injected in QueueListener to resolve TService. And each TService resolved should be already like what you want
lightweight and stateless as a handler for payload
With regards to
Documentation says:
What the document says might not be relevant now since you are not using scoped services. But in case you want to know the reason:
It's dangerous to resolve a scoped service from a singleton.
Singleton is a special kind of scope. Singleton services are created and cached within a "root" scope of the container, which is essentially the container itself.
If you resolve scoped service from singleton, the lifetime / scope where the service instance is resolved and cached is likely to be the "root" scope. This leads to a problem where the scoped service instance being cached inside the container, and shared across multiple client requests.
This is dangerous, because scoped services are supposed to be
Scoped lifetime services (AddScoped) are created once per client request (connection).
I don't wanted my singleton class depending on the IServiceProvider directly.
So I've used a custom factory to accomplish this goal.
May this code example help others:
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IScopedBar, ScopedBar>();
services.AddSingleton<IScopedServiceFactory<IScopedBar>, ScopedServiceFactory<IScopedBar>>(
(provider) => {
var scope = provider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IScopedBar>();
return new ScopedServiceFactory<IScopedBar>(() => new ScopedService<IScopedBar>(scope, service));
});
services.AddSingleton<ISingletonFoo, SingletonFoo>();
}
// ...
}
public interface ISingletonFoo
{
void DoSomethingUsingScopedServices();
}
public class SingletonFoo : ISingletonFoo
{
private readonly IScopedServiceFactory<IScopedBar> _barFactory;
public SingletonFoo(IScopedServiceFactory<IScopedBar> barFactory)
{
_barFactory = barFactory;
}
public void DoSomethingUsingScopedServices()
{
using var scopedService = _barFactory.CreateService();
scopedService.Service.DoSomething();
}
}
public interface IScopedBar
{
void DoSomething();
}
public class ScopedBar : IScopedBar
{
public void DoSomething()
{
// Do something
}
}
public interface IScopedService<T> : IDisposable
{
T Service { get; }
}
public interface IScopedServiceFactory<T>
{
IScopedService<T> CreateService();
}
public class ScopedService<T> : IScopedService<T>
{
private readonly IDisposable _scope;
public ScopedService(IDisposable scope, T service)
{
_scope = scope;
Service = service;
}
public T Service { get; }
public void Dispose()
{
_scope.Dispose();
}
}
public class ScopedServiceFactory<T> : IScopedServiceFactory<T>
{
private readonly Func<IScopedService<T>> _serviceFactory;
public ScopedServiceFactory(Func<IScopedService<T>> serviceFactory)
{
_serviceFactory = serviceFactory;
}
public IScopedService<T> CreateService()
{
return _serviceFactory();
}
}
Here is my code to consume scoped service:
public interface IScopedResolver<T> where T: class
{
TResult Resolve<TResult>(Func<T, TResult> dataFactory);
Task<TResult> ResolveAsync<TResult>(Func<T, Task<TResult>> dataFactory);
}
Implement class:
public class ScopedResolver<T> : IScopeResolver<T> where T: class
{
private readonly IServiceProvider _provider;
public ScopedResolver(IServiceProvider provider)
{
_provider = provider;
}
public TResult Resolve<TResult>(Func<T, TResult> dataFactory)
{
using IServiceScope scope = _provider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<T>();
return dataFactory(service);
}
public async Task<TResult> ResolveAsync<TResult>(Func<T, Task<TResult>> dataFactory)
{
using var scope = _provider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<T>();
return await dataFactory(service);
}
}
Register at startup:
services.AddSingleton(typeof(IScopedResolver<>), typeof(ScopedResolver<>));
using ScopedResolve:
public class ServiceA
{
private readonly IScopedResolver<DbContext> _context;
public ServiceA(IScopedResolver<DbContext> context)
{
_context = context;
}
public async Task<List<ClassOne>> GetListAsync()
{
return await _context.ResolveAsync(async s => await s.Set<ClassOne>().ToListAsync());
}
}

DBContext is null when getting in implementation of IHostedService

I have made an implementation of IHostedService where I will load a cache when my application is starting up. In that implementation of IHostedService I need the Context for the DB, but it is null when I try to get it.
I am loading my service in StartUp ConfigureServices in the last line:
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DatabaseConnection")));
...
services.AddHostedService<InitializeCacheService>();
And my service look like this (I am getting the cache but not the context):
public class InitializeCacheService : IHostedService
{
private readonly IServiceScopeFactory _scopeFactory;
public InitializeCacheService(IServiceScopeFactory scopeFactory)
{
this._scopeFactory = scopeFactory;
}
public Task StartAsync(CancellationToken cancellationToken)
{
using (var scope = _scopeFactory.CreateScope())
{
var cache = scope.ServiceProvider.GetService<IMemoryCache>();
var context = scope.ServiceProvider.GetRequiredService<MyContext>();
CacheLogic cacheLogic = new CacheLogic(cache, context);
cacheLogic.LoadProfileCache();
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
What am I doing wrong?
Thanks very much in advance :-)
UPDATE
This is actually working, the problem is that I missed the initialization of the context in CacheLogic :-| Sorry about that!
But now it is working then i get the error below when I query in CacheLogic, an that might be because it is async and I handle over the Context in a time limited scope in the InitializeCacheService?
But After rethinking my solution I think that the CacheLogic shall be a singleton, and then my problem is how to get access to cache and DBContext in a singleton (I will create a new question for that).
System.ObjectDisposedException: 'Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
ObjectDisposed_ObjectName_Name'

Getting DbContext via Configuration with DbContextOptions

I am trying to get the DbContext I registered with options via services.AddDbContext(...) on the service provider of the project, but when calling configuration.Get<ModelContext> it can not be constructed as the options apparently weren't provided and therefore also no database provider is given.
I am using ASP.NET Core 2.2 with Entity Framework Core 2.2.3 and my DbContext is defined in a separate project.
My DbContext:
public class ModelContext : DbContext
{
public ModelContext(DbContextOptions<ModelContext> options) : base(options) { }
public ModelContext() { }
}
I did not override OnConfiguring(DbContextOptionsBuilder) in ModelContext.
public class StartUp
{
public void ConfigureServices(IServiceCollection services)
{
public services.AddEntityFrameworkSqlServer();
services.AddDbContext<ModelContext>(options => options.UseSqlServer(modelConnectionString));
}
}
In the controller (or anywhere really) I call public HomeController(IConfiguration configuration) => _modelContext = configuration.Get<ModelContext>(); which throws the unexpected exception.
What I specifically get is an InvalidOperationException with the message:
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 object in its constructor and passes it to the base constructor for DbContext.
According to the documentation I read and examples I looked at, the ModelContext should be created with the options I defined when calling AddDbContext<ModelContext>. Is the Get method the wrong one to use?
After configuring the db context service in "ConfigureServices" method of the Startup.cs file with something like this :
var connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<BottinContext>(options => options.UseSqlServer(connectionString)) ;
Simply add a :
ModelContext db
parameter to the constructor of your controller and let DI magic happen.
If you've got many controllers and wish to simplify things, you can use a base contructor that holds the db context
public BaseController(ModelContext context /* as well as other injections */)
{
_db = context;
}
internal ModelContext _db;
you are trying to get dbContxt instance in a wrong way. Get method is not used to get instance of dbContext object that you registered with dependency injection container.
if you want to get instance of your dbContext class that you registered you can inject it through construction injection for example
public class RepositoryWrapper : IRepositoryWrapper
{
private readonly ModelContext _modelContext;
public RepositoryWrapper(ModelContext modelContext)
{
_modelContext= modelContext;
}
}
is something i am doing in my project.

Categories

Resources