Home > Software design >  How to use DbContext in a singleton class?
How to use DbContext in a singleton class?

Time:07-07

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, 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.
  • 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 the EUMemberChecker at the time you create the cache. This might mean you need to inject configuratino values into EUMemberChecker, such as the connection string, which is something DbContext 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 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 quering 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.

  • Related