Reputation: 2015
I implemented a class EUMemberChecker
which is responsible of checking if a country is a member of the EU.
In order to do its job, the class contains a method public bool IsEUMember(string country)
.
The data used to check if a country is a member of the EU is stored in a PostgreSQL database table.
I want to make this class available via DI by adding it as a singleton service via AddSingleton
. The reason for this is that it should only load the EU members from the database once on application startup. If I would add this class via AddScoped
, every single instance of it would need to have its own list of EU members loaded from the database, which would be quite an overhead in my opinion.
My problem is that I cannot add this class as a singleton because it uses my DbContext
which is added as a scoped service. By doing it anyway, it causes the runtime error Dependency ...DatabaseContext {ReturnDefault} as parameter "context" reuse CurrentScopeReuse {Lifespan=100} lifespan shorter than its parent's: singleton ... {ReturnDefault} as parameter ...
.
Therefore, it seems like I have to add my class as a scoped service, resulting in an additional database call to load the EU member countries for every instance of the class.
Am I applying the Single Responsibility Principle incorrectly here or how can I get around this problem? How can I ensure that the EU members are not loaded from the database multiple times for no good reason?
Upvotes: 4
Views: 2133
Reputation: 172646
There are several solutions to solve this. Here are the options I could think of:
EUMemberChecker
scoped, but pull the cache part out of the class, and inject it as a Singleton service.EUMemberChecker
part of your Composition Root to allow injecting the Container instance (e.g. IServiceProvider
) into that class. This allows creating a scope instance from which you can resolve, for instance, the DbContext
. If the class contains business logic, it'd be good to extract that from the class and inject that into the class as well; you wish to keep the amount of code inside your Composition Root as small as possible.DbContext
manually inside the EUMemberChecker
at the time you create the cache. This might mean you need to inject configuration values into EUMemberChecker
, such as the connection string, which is something DbContext
obviously needs.services.AddSingleton(c => new EUMemberChecker(loadedMembers))
.EUMemberChecker
directly at startup by calling some sort of Initialize
method. Either the loaded members can be supplied to the method, or you can pass in the DbContext
so that Initialize
can do the querying internally. This DbContext
can be resolved from the container at startup, probably by resolving it from a manually created scope.Which option is best, depends on a lot of implementation details, so you will have to decide which one best suits your needs.
Upvotes: 6