0

I have a database context service AppDbContext that has been registered with the framework service container. I also have a class CustomerSearch with several methods for retrieving a list of Customer objects based on a searching criterion via the injected AddDbContext. As the CustomerSearch methods just read and does not write, I think it is a good candidate as a static class and no race condition will occur.

Question

If I convert the CustomerSearch class to a static class, how can I inject the AppDbContext?

Community
  • 1
  • 1
Second Person Shooter
  • 14,188
  • 21
  • 90
  • 165

1 Answers1

3

Constructor Injection is the preferred way of practicing Dependency Injection. The following is an example of Constructor Injection:

// Component that requires the AppDbContext dependency to be supplied
// using the component's constructor. This is Constructor Injection.
public class CustomerSearch 
{
    private readonly AppDbContext context; // private field to store dependency

    public CustomerSearch(AppDbContext context) // ctor with required dependency
    {
        this.context = context;
    }
}

Constructor Injection however is only possible on instance constructors, not on class constructors (a.k.a. static constructors). This rules out static classes for doing Constructor Injection.

Instead of making a class static, the typical DI optimization for immutable, stateless classes is to keep the class non-static, but make sure only a single instance is created. In that case we say the class has a Singleton lifestyle (not to be confused with the Singleton design pattern).

However, since classes that apply Constructor Injection store their incoming dependencies in private fields, it will cause those dependencies to be kept alive for as long as their consuming class. This will cause problems in case that dependency is supposed to have a shorter lifespan than its consumer. This problem is commonly referred to as a Captive Dependency. In other words, a class that stores its dependencies should not outlive its dependencies.

This makes the Singleton lifestyle an unlikely lifestyle for your CustomerSearch, since a DbContext should not be reused by multiple requests. This inevitably leads to bugs. Since a DbContext is at most reused within a single request, that means that its consumers, such as CustomerSearch should as well live for -at most- as long as a single web request, and should not be reused by different web requests.

A totally different solution is to apply Method Injection and provide the DbContext through a public method on the CustomerSearch method. This would allow CustomerSearch to become static in case its methods just use the supplied dependencies, but never store them. For instance:

// Component is now static and the AppDbContext dependency is now supplied
// using Method Injection. Since CustomerSearch is static, it can't have
// a Constructor with dependencies.
public static class CustomerSearch 
{
    public static Customer[] Search(
        string lastName, // one or multiple parameters for the query
        AppDbContext context) // one or multiple dependencies
    {
        // use context to find customers and return
    }
}

Be very careful with this approach though, since this practice can easily cause a snowball effect throughout the application every time a new dependency is added to one of the methods of such static class. Method Injection should typically only be used in rare places. A common place for Method Injection is in Domain Entities, where the Entity stores its data, but contains domain methods that accept their required dependencies through Method Injection. For instance:

// Customer is a Domain Entity
public class Customer
{
    // Id and Name are properties of customer.
    public Guid Id { get; private set; }
    public string Name { get; private set; }

    // The constructor is used to create a new instance with its values
    public Customer(Guid id, string name)
    {
        ...
    }

    // Since Constructor Injection is not practical on Domain Entities,
    // Method Injection provides a valuable alternative.
    public void RedeemVoucher( 
        Voucher voucher, // data object
        IVoucherRedemptionService service) // dependency
    {
        service.ApplyRedemptionForCustomer( // use dependency
            voucher, 
            this.Id);
    }
}

Note: this is an example from the book Dependency Injection Principles, Practices, and Patterns (§ 4.3).

When dealing with components however (the classes that contain the application's behavior), Constructor Injection is typically the best solution, because it makes dependencies an implementation detail of the class, rather than exposing them through its abstraction.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Considering the issue due to Captive Dependency, is instatiating `AppDbContext` manually with `using` block in each `CustomerSearch`'s method a good alternative here? It means I don't use DI to have an instance of `AppDbContext` in `CustomerSearch` class. – Second Person Shooter Feb 08 '18 at 09:19
  • Absolutely, you can make `DbContext` an _ephemeral disposable_, which means you create and destroy it in the same method. But there are pitfalls to doing that, such as: do you want _each_ class to manage its own `DbContext` instance? Once you change the ctor of your DbContext, this will snowball throughout all classes that create it. – Steven Feb 08 '18 at 09:45
  • I found there is a `AddDbContextPool` extension method available. Unfortunately I can make the service lifetime of `CustomerSearch` be either `Scoped` or `Transient` but not `Singleton`. – Second Person Shooter Feb 08 '18 at 10:01