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?
CodePudding user response:
There are several solutions to solve this. Here are the options I could think of:
- Make the
EUMemberChecker
scoped, but pull the cache part out of the class, and inject it as a Singleton service. - Make
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, theDbContext
. 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. - Ignore and suppress the warning, as you seem to be sure that this doesn't cause any problem in your specific case. Note that with MS.DI, there likely isn't an easy way to suppress this error.
- Construct the
DbContext
manually inside theEUMemberChecker
at the time you create the cache. This might mean you need to inject configuratino values intoEUMemberChecker
, such as the connection string, which is somethingDbContext
obviously needs. - Load the data from the database before making your container registrations and supply this cache manually upon registration. e.g.:
services.AddSingleton(c => new EUMemberChecker(loadedMembers))
. - Initialize
EUMemberChecker
directly at startup by calling some sort ofInitialize
method. Either the loaded members can be supplied to the method, or you can pass in theDbContext
so thatInitialize
can do the quering internally. ThisDbContext
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.