C#中继承场景下依赖注入的SOLID合规实现方案咨询
Hey there! Let's work through your problem step by step—your core issue is that your base Repository<T> class's constructor dependencies are forcing every derived repository to update when the base changes, which clearly violates the Open/Closed Principle (OCP). Let's break down your proposed solutions against SOLID, then cover the best path forward that meets all your requirements.
Analysis of Your Proposed Solutions
1. Service Aggregator Pattern
This approach aligns well with SOLID principles, and its drawbacks are solvable:
- Compliance with SOLID:
- Follows OCP: When the base repository needs new dependencies, you only update the
IRepositoryDependenciesinterface and its implementation—derived repositories never need changes, since they only depend on the abstract aggregator. - Follows Dependency Inversion Principle (DIP): Both base and derived repositories depend on an abstraction (
IRepositoryDependencies) instead of concrete services likeIServiceAorIServiceB.
- Follows OCP: When the base repository needs new dependencies, you only update the
- Solving the Dynamic Proxy Issue: You don't need Castle DynamicProxy to make this work smoothly. Instead, create a lightweight aggregator that uses your DI container's
IServiceProviderto resolve dependencies on demand:public class RepositoryDependencies : IRepositoryDependencies { private readonly IServiceProvider _serviceProvider; public RepositoryDependencies(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IServiceA ServiceA => _serviceProvider.GetRequiredService<IServiceA>(); public IServiceB ServiceB => _serviceProvider.GetRequiredService<IServiceB>(); // Add new dependencies here later without touching derived classes }
2. Builder Pattern
This is also SOLID-compliant, but its DI registration pain point can be mitigated:
- Compliance with SOLID:
- Follows OCP: Adding base dependencies only requires updating the base builder class. Derived builders inherit this logic automatically, and users only need to modify their builders if they want custom behavior.
- Follows Single Responsibility Principle (SRP): Separates repository construction logic from the repository's core data access responsibilities.
- Solving DI Registration: Use reflection to scan for all repository classes and auto-register their corresponding builders (e.g., map
FooRepositorytoFooRepositoryBuilderby naming convention). For ASP.NET Core, you can wrap this logic in an extension method to keep registration clean.
3. Property Injection
This solves the OCP problem but has SOLID and best practice caveats:
- Partial Compliance:
- Follows OCP: Base class dependency changes don't break derived classes.
- Violates encapsulation: Public setters expose internal dependencies to external code, which breaks core object-oriented principles (a subset of SOLID's intent around maintaining clean boundaries).
- Risk of invalid state: If dependencies are required (not optional), property injection can leave your repository in an invalid state if the DI container fails to inject a property. This violates the principle of ensuring objects are always valid once constructed.
Recommended Solution: Optimized Service Aggregator Pattern
This approach checks all your boxes while staying fully SOLID-compliant:
Step 1: Define the Dependency Aggregator
public interface IRepositoryDependencies { IServiceA ServiceA { get; } IServiceB ServiceB { get; } // Add future dependencies here without modifying derived classes }
Step 2: Update the Base Repository
public abstract class Repository<T> { protected readonly IRepositoryDependencies _dependencies; protected Repository(IRepositoryDependencies dependencies) { _dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies)); } }
Step 3: Implement the Aggregator with DI Resolution
Use the lightweight implementation I mentioned earlier to avoid manual dependency wiring:
public class RepositoryDependencies : IRepositoryDependencies { private readonly IServiceProvider _serviceProvider; public RepositoryDependencies(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IServiceA ServiceA => _serviceProvider.GetRequiredService<IServiceA>(); public IServiceB ServiceB => _serviceProvider.GetRequiredService<IServiceB>(); }
Step 4: Register the Aggregator and Repositories
- Register the aggregator in your DI container:
services.AddScoped<IRepositoryDependencies, RepositoryDependencies>(); - Auto-register all repositories via reflection (mimicking ASP.NET Core's controller registration):
var repositoryAssembly = typeof(FooRepository).Assembly; foreach (var repoType in repositoryAssembly.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && t.BaseType != null && t.BaseType.IsGenericType && t.BaseType.GetGenericTypeDefinition() == typeof(Repository<>))) { services.AddScoped(repoType); }
Step 5: Derived Repository with Custom Dependencies
Users can easily add custom dependencies to their repositories without breaking base class changes:
public class FooRepository : Repository<Foo> { private readonly ICustomFooService _customService; public FooRepository(IRepositoryDependencies dependencies, ICustomFooService customService) : base(dependencies) { _customService = customService ?? throw new ArgumentNullException(nameof(customService)); } }
This solution meets all your requirements:
- Easy to use: Derived repositories only need to inject the aggregator (plus custom dependencies if needed).
- DI-friendly: All components are resolved through your DI container.
- OCP-compliant: Base class dependency changes don't affect derived repositories.
- Auto-registration: Reflection-based registration works just like ASP.NET Core controllers.
- Custom dependencies: Users can add their own dependencies to derived repositories seamlessly.
内容的提问来源于stack exchange,提问作者Maxime Gélinas

