-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Infrastructure: Add parameter objects and service rewriting code to avoid breaking base class D.I. constructors in patch/point releases #7465
Comments
I'm super curious what you come up with. I've hit this in my own unrelated architectures. The If you don't see any value to inheritance, or even want to make it impossible, Just thinking out loud. |
How about just passing a single IServiceProvider parameter to all base services, and to allow customization of per provider create a wrapper IServiceProvider for each provider. Edit: I've meant IServiceProvider not IServiceCollection |
@jnm2 @popcatalin81 Passing an IServiceProvider is probably what we will end up doing, but also what we hope to avoid doing because it results in use of the service locator pattern, which then obscures dependencies. I suspect we will use this pattern along with adding [Obsolete] to the existing constructor while at the same time introducing a new constructor with the new dependency added explicitly. Then at next major version rev we will remove the obsolete constructor. |
Discussed in design meeting and decided to try the parameter object approach for services, using sealed classes. Considered composition, but this would be a big change to the architecture and design of certain services. |
This is a proof-of-concept implementation of parameter objects for service dependencies. Issue #7465. Notes: - Parameter class is sealed. - Ended up going with Clone on the parameters object--cleaner than other things I tried, and general purpose. We can add overloads of Clone as needed when doing point releases without breaking existing code. (May want to consider whether parameter should be options.) - Registration is not try-add and is of concrete class
This is a proof-of-concept implementation of parameter objects for service dependencies. Issue #7465. Notes: - Parameter class is sealed. - Ended up going with With methods on the parameters object--cleaner than other things I tried, and general purpose. - Registration is back to try-add again because apparently Add results in multiple services registered in the ServiceCollection...
This is a proof-of-concept implementation of parameter objects for service dependencies. Issue #7465. Notes: - Parameter class is sealed. - Ended up going with With methods on the parameters object--cleaner than other things I tried, and general purpose. - Registration is back to try-add again because apparently Add results in multiple services registered in the ServiceCollection...
Issue #7465 This change introduces a new class ServiceCollectionMap that is used by providers and by our service-adding methods. The new class builds a map from service type to indexes of the ServiceDescriptors in the list for the given type. This allows all the TryAdd calls that we make to work without scanning the list every time. In addition, this type has a method for re-writing services to do property injection when we need to add a service dependency without breaking a constructor in a patch or point release. This is used by making a call at the end of our service registrations like so: ```C# ... .TryAddScoped<IResultOperatorHandler, ResultOperatorHandler>() .DoPatchInjection<IValueGeneratorSelector>(); ``` Also, ReplaceService code has been updated to ensure that service-injection is also run on any replaced services, since these services may also inherit from our base classes.
Issue #7465 This change introduces a new class ServiceCollectionMap that is used by providers and by our service-adding methods. The new class builds a map from service type to indexes of the ServiceDescriptors in the list for the given type. This allows all the TryAdd calls that we make to work without scanning the list every time. In addition, this type has a method for re-writing services to do property injection when we need to add a service dependency without breaking a constructor in a patch or point release. This is used by making a call at the end of our service registrations like so: ```C# ... .TryAddScoped<IResultOperatorHandler, ResultOperatorHandler>() .DoPatchInjection<IValueGeneratorSelector>(); ``` Also, ReplaceService code has been updated to ensure that service-injection is also run on any replaced services, since these services may also inherit from our base classes.
React to dotnet/efcore#7465
React to dotnet/efcore#7465
Issue #7465 This change introduces parameter objects for the service dependencies of all public services. This allows new dependencies to be added without breaking derived classes that call the base class constructor. For factories, these dependency objects are passed down into the services created by the factory. This means that we also don't break by adding parameters to the constructors called by the factory. This is the main reason for using this approach rather than just the service rewriting approach. The pattern is not used for internal services since no code should be inheriting from these classes. However, the service re-writer code is still in place, which means that we have some flexibility to add dependencies to internal services even in patch releases where we are being very strict about binary compatibility. Some work remains still to do: - Add With methods to all ...Dependencies objects to allow for service replacement - Add sugar for registration - Re-visit service scopes
Issue #7465 This change introduces parameter objects for the service dependencies of all public services. This allows new dependencies to be added without breaking derived classes that call the base class constructor. For factories, these dependency objects are passed down into the services created by the factory. This means that we also don't break by adding parameters to the constructors called by the factory. This is the main reason for using this approach rather than just the service rewriting approach. The pattern is not used for internal services since no code should be inheriting from these classes. However, the service re-writer code is still in place, which means that we have some flexibility to add dependencies to internal services even in patch releases where we are being very strict about binary compatibility. Some work remains still to do: - Add With methods to all ...Dependencies objects to allow for service replacement - Add sugar for registration - Re-visit service scopes
React to dotnet/efcore#7465
React to dotnet/efcore#7465
When a service is overridden by a provider, the provider must call the base service constructor. This means it becomes a breaking change when base service gets new dependencies such that its constructor needs to change.
We previously implemented a mechanism whereby additional services could be injected as part of the provider service resolution mechanism. This worked because provider services were always registered through a delegate, and so we could add code to that delegate as needed to inject services. Following #7457 services are registered by the provider explicitly in the container, which is much preferable for perf, but means that there is no hook to inject services.
The text was updated successfully, but these errors were encountered: