Entity Framework Core Internals: DbContext Instantiation and Initialization

Introduction

Continue the 1st post: Entity Framework Core Internals: High-Level Overview, in today’s post, we will take a deeper look inside the DbContext class to find out how instantiation and initialization steps work.

DbContext Instantiation

Normally, when working with your DbContext, you often start your query with a query root, you can use the generic method Set<TEntity>() or you can also predefine some DbSet properties to name the Sets and don’t need to specify the generic type every time you want to work with. For example:

Now you can access a Set in 2 ways and both should work no differently to serve your query.

Hold a sec, you might be thinking when working with a class if you just define a property and never initialize it, oftentimes you will face the NullReferenceException, so why does the code at line 10 never throw that exception. Let’s do some experiments to find out why?

Let’s try to invent our own DbContext called FakeBloggingContext and with no doubt, we faced the exception:

Let’s back to using our own real DbContext again and you figure that out the DbSet was initialized so we will never face the NullReferenceException here.

Now you might be wondering who did that? I know you already know the answer: EF Core, but how? Let’s dig deep into the EF Core source code next.

Let’s take a look at one of the DbContext constructors:

Ignore the ServiceProviderCache class, we’ll take a look at that later in our future post when we talk about Dependency Injection, for now, focus on line 130 where EF Core tries to initialize all the DbSet properties you defined in your DbContext.

Drill down to InitializeSets method of the DbSetInitializer class (default implementation of the IDbSetInitializer interface).

It calls the IDbSetFinder _setFinder dependency to find all the DbSets information you defined and then keep the ones that have the Setter property.

Let’s take a look at the DbSetFinder class (default implementation of the IDbSetFinder interface) to see what it does:

It uses Reflection to find all the DbSet properties you defined and captures needed information to use later. If the property has the setter method, it uses the ClrPropertySetterFactory class to create a method that can be used to initialize the DbSet property. Let’s take a look at the ClrPropertySetterFactory class next.

The code of the ClrPropertySetterFactory class is quite large so I don’t capture it here, Basically, It uses Expression APIs to build an Expression Tree and compile to a method that will be used to initialize the DbSet property.

Now, EF Core has all the information it needs, let’s go back to the DbSetInitializer class, it invokes the Setter method for each DbSet property:

Ignore the IDbSetCache as we all know EF Core uses cache a lot, let’s pay attention to the IDbSetSource _setSource dependency.

Go to the DbSetSource class (default implementation of IDbSetSource interface), and you should see it creates and returns an instance of the InternalDbSet<TEntity> class.

Take a quick look at the InternalDbSet class, you will see it implements all the methods that we usually work with DbSet and IQueryable.

DbContext Initialization

Because DbContext is lazy, it doesn’t do heavy stuff until you start using it, when you start working with the DbContext, it usually requires one of the properties of the IDbContextServices ContextServices property, let’s see how IDbContextServices ContextServices is initialized the first time it is used.

It creates a DbContextOptionsBuilder instance using the DbContextOptions _options (the one you pass into the constructor or default if you use the parameterless constructor) and then calls the OnConfiguring method.

In our BloggingContext example, the OnConfiguring method will be invoked and the Sqlite Database Provider is registered here:

After calling the OnConfiguring method, next it builds fully the Internal Service Provider with the flag providerRequired: true this time. We will talk about Internal Service Provider in the near future post.

After the Internal Service Provider is created and cached, it creates a scope and resolves the IDbContextServices, so basically each DbContext instance will have its own scope.

Next, it initializes the IDbContextServices instance by calling the Initialize method, let’s check what it does by opening the DbContextServices class (default implementation of the IDbContextServices interface).

It validates and throws an exception if more than 1 DatabaseProvider is registered or there is no DatabaseProvider or the first DatabaseProvider is not configured yet.

Summary

That comes to the end of how DbContext is instantiated and initialized, as mentioned so many times in the post, we will go through the process of creating the Internal Service Provider, what Core Services are, and how we can make EF Core work differently with different Database Provider in the next post.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top