I have started to learn c# recently and I am confused with the reason of existance of the method .CreateScope()
.
I have worked on JAVA previously and I can't relate to it. If we need a service as a dependency we can inject it in contructor and then we have it for use.
From my understanding, we can create a new service scope using it and that may be useful at the time of some bootstraping of app. I read through this post of SO , this one and this one from MS but I really think that I have not yet understood its significance.
I see people creating
public constructor( IServiceScopeFactory scopeFactory,......)
{
this.scopeFactory = scopeFactory;
}
and then using it in a method as
using var scope = scopeFactory.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Send(....);
but we can inject the IMediator as well, right?
Please help me here. Thanks !
CodePudding user response:
The dependency injection container Microsoft.Extensions.DependencyInjection that is often used as the default in modern .NET applications (e.g. ASP.NET Core) supports three different lifetimes of services: Transient, singleton and scoped. A service’s lifetime decides how long a particular instance lives and how it may be reused when multiple things depend on the service.
The two simpler lifetimes are transient and singleton: A service having a transient lifetime essentially means that a new service will be created whenever something else is constructed that depends on it. For example, if you have two services A and B that depend on a transient service D, then there will be two instances of D: One for A, and one for B. If D was a singleton service however, then both A and B would get the same instance. Services registered as singletons will only ever have a single instance and that single instance will be reused every time. The single service instance will stay in memory for the whole lifetime of your application and is only removed when the application shuts down.
Now, transient and singleton already help a lot of the time. But there are cases, where you want a mix between these. A common example are database connections: Connecting to the database is somewhat expensive, so you usually want to avoid creating a connection for every single query you run. But on the other hand, you don’t want to keep connections open forever and you also don’t want to restrict yourself to a single connection for your whole application because database connections usually do not allow parallel queries – something which you want to do if your application has concurrent users.
So the idea is to have one connection for each user that gets reused while the user is still there. But every user gets it own independent connection. In a web context, e.g. with ASP.NET Core applications, a “user” usually means a single request: So every incoming HTTP request gets its own database connection which is reused as much as possible for that single request.
To achieve this, the dependency injection container supports scopes. A scope is basically a construct that keeps instances of services registered with a scoped lifetime around for as long as the scope is still open. In ASP.NET Core for example, a new scope is created whenever a HTTP request comes in. And services that require a database connection will get the database connection from that request scope. This makes sure that there is only a single database connection per request but every request is still independent from others.
In frameworks like ASP.NET Core, you usually don’t need to deal with the creation of scopes. The framework will take care of it and you can just resolve scoped services within your normal contexts and everything will work. There is one exception though: Singleton services.
If you have a singleton service, then that service instance is independent of scopes. It gets created sometime early in your application and that single instance is shared regardless of the lifetime of service scopes. That’s usually what you want to do with singletons, so everything is fine. But what happens when the singleton service now needs a database connection, which is a scoped dependency? Since the singleton service itself is not part of a scope, how should the container resolve a scoped dependency? Which scope should it use? What if there are multiple parallel scopes which all have a database connection? What if there is no scope at all?
The answer is basically “no”: In general, you cannot have a singleton service depend on a scoped service and attempting to do so will fail at runtime. Instead, if your singleton service requires a scoped dependency, e.g. a database connection, then it should create its own scope where it can resolve the scoped dependencies from. And for that, it can use the IServiceScopeFactory
:
public class MySingletonService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public MySingletonService(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void DoSomething()
{
// create a new scope
using (var scope = _serviceScopeFactory.CreateScope())
{
// resolve a database connection
var db = scope.ServiceProvider.GetService<IDatabaseConnection>();
// do something with it
db.RunQuery();
// the using make sure that the scope is removed afterwards,
// cleaning up all created instances, and e.g. closing database
// connections
}
}
}