Search code examples
c#dependency-injectionioc-containersimple-injector

How to determine which lifestyle to use in Simple Injector


My application uses Entity Framework. Since I want my DbContext to be reused across a single request, I registered it as Lifestyle.Scoped, as shown below:

container.Register<MyDbContext>(Lifestyle.Scoped);

Other classes get this MyDbContext injected. As example, see the following repository:

ApplicationsRepository is:

public class ApplicationsRepository : IApplicationsRepository
{
    private readonly MyDbContext _dbContext;

    public ApplicationsRepository(MyDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public void Save()
    {
        _dbContext.Save();
    }

    public Application GetByName(string appName)
    {
        var dbApplication = _dbContext.APPLICATIONS.FirstOrDefault(a => a.NAME == appName);

        return ApplicationMapper.MapApplicationFromAPPLICATIONS(dbApplication);
    }
}

But there are countless other classes that either take a dependency on MyDbContext. Other classes might not depend on MyDbContext directly, but do get injected with a class that does depend on MyDbContext.

My questions is: what kind of lifestyle management should I use for these classes and how implements it?


Solution

  • There are 3 basic lifestyles to choose from, Transient, Scoped and Singleton, as you already learned by reading this.

    Which lifestyle to choose for a particular component, depends on several factors:

    • Whether or not the component can be used safely across requests/threads
    • Whether or not the component contains state that needs to be shared across threads
    • The lifestyle of the component's dependencies.

    First of all, the lifestyle your component needs is a choice you need to make based on how that component is designed. Some components simply can't be reused and should always be re-created when requested. This typically holds for framework types such as MVC Controllers. Although there is typically one controller per request, it is possible to request other controllers and this requires new instances to be created. This is equivalent to the Transient lifestyle.

    Other components or classes that you register need to be reused. Unit of Work implementations such as Entity Framework's DbContext will typically need to be reused for the duration of a complete request. You can read a detailed discussion about why you want to reuse a DbContext here. This is equivalent to the Scoped lifestyle.

    Other components are completely stateless or immutable and can be reused by all threads in the application in parallel without any trouble. Other components might be statefull or mutable, but are designed with thread-safety in mind. They might implement an application-wide cache that needs to be updated, and access to the component is protected with locks. This means you only have one instance reused throughout the application. This is equivalent to the Singleton lifestyle.

    Dependencies however complicate lifestyle choice of a component, because a component's lifestyle should never be longer than any of its dependencies. Failing to obey to this rule, causes Captive Dependencies, or Lifestyle Mismatches as Simple Injector calls them.

    This means that even if you determined a component to be eligible to be a Singleton, it can only be as long as its shortest dependency. In other words, if the component has a Scoped Dependency, it itself can only be either Scoped or Transient. Simple Injector will detect if you misconfigure this.

    This does mean however that the choice you make for a component, does propagate up the call stack to the component's consumers.

    In general, this results in a structure where the leaf components in an application's object graphs are Scoped and Singleton, while the root types are Transients. If you are structuring your object graphs in this way, you are following the Closure Composition Model.

    There is an alternative DI Composition Model, and it is called the Ambient Composition Model. When practicing this model you, for the most part, keep state out of your components, and instead store state in Ambient State, which is controlled by the Composition Root. Both composition models have their advantages and disadvantages.