You can improve data access performance in Entity Framework Core in several ways. These include enabling eager loading, disabling lazy loading, using streaming instead of buffering, and disabling change tracking. In this article, we will explore some of the tips and tricks that can help you improve the performance of your ASP.NET Core 7 applications that make use of EF Core 7.
To work with the code examples provided in this article, you should have Visual Studio 2022 Preview installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 Preview here.
Create an ASP.NET Core minimal Web API project in Visual Studio 2022 Preview
First off, let’s create an ASP.NET Core project in Visual Studio 2022. Following these steps will create a new ASP.NET Core Web API 7 project in Visual Studio 2022:
- Launch the Visual Studio 2022 Preview IDE.
- Click on “Create new project.”
- In the “Create new project” window, select “ASP.NET Core Web API” from the list of templates displayed.
- Click Next.
- In the “Configure your new project” window, specify the name and location for the new project.
- Optionally check the “Place solution and project in the same directory” check box, depending on your preferences.
- Click Next.
- In the “Additional Information” window shown next, under Framework, select .NET 7.0 (Preview).
- Uncheck the check box that says “Use controllers…” since we’ll be using minimal APIs in this example. Leave the “Authentication Type” set to “None” (default).
- Ensure that the check boxes “Enable Docker,” “Configure for HTTPS,” and “Enable Open API Support” are unchecked as we won’t be using any of those features here.
- Click Create.
We’ll use this ASP.NET Core 7 Web API project to work with Entity Framework Core 7 in the subsequent sections of this article.
What is Entity Framework Core?
Entity Framework is Microsoft’s object-relational mapper (ORM) for .NET. Entity Framework Core is the open-source, cross-platform version of Entity Framework for .NET Core.
Entity Framework Core makes it easier to implement data access in your .NET Core applications because it allows you to work with the database using .NET objects. EF Core lets you write code to execute CRUD actions (create, read, update, and delete) without understanding how the data is persisted in the underlying database. Using EF Core, you can more easily retrieve entities from the data store, add, change, and delete entities, and traverse entity graphs.
EF Core performance best practices
You can help EF Core perform these data access operations more speedily by taking advantage of a few best practices. We’ll discuss five of these best practices below.
Disable change tracking for read-only scenarios
Whenever you query entities in your DbContext, the context tracks the returned objects so that you can alter them and preserve the changes. If the query is a read-only query, i.e., if no changes will be made to the returned data, then the context is not required to perform that task. You should disable change tracking if it is not required.
You can disable change tracking for individual queries by including the AsNoTracking method in the query. When the AsNoTracking method is used, EF Core will skip the extra effort of tracking the entities, thereby improving performance (especially for queries involving large numbers of entities).
Most importantly, you do not need change tracking when you only intend to retrieve data in your application. In other words, if you only want to retrieve data from the data context, without inserting, updating, or deleting data, then you don’t need this feature to be turned on. You can disable object tracking by adding the following code to your data context class.
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
The bottom line is that queries that use AsNoTracking will run faster than queries that don’t use it. However, remember that you must never use AsNoTracking in queries that insert, edit, or delete entities. Furthermore, if you need to insert, edit, or delete data using the data context, you should avoid specifying the QueryTrackingBehavior at the data context level.
Retrieve only the data you need
When dealing with massive volumes of data, you should strive to retrieve only the required records for the specific query. When fetching data, you should use projections to pick just the required fields. You should avoid retrieving unnecessary fields. The following code snippet shows how to obtain data in a paged fashion. Notice how the beginning page index and page size have been used to choose just the required data.
int pageSize = 50, startingPageIndex = 1;
var dataContext = new OrderProcessingDbContext();
var data = dataContext.Orders.Take(pageSize)
.Skip(startingPageIndex * pageSize)
Split your large data context into many smaller data contexts
The data context in your application represents your database. Hence, you may wonder whether the application should have only one or more data contexts. In Entity Framework Core, the startup time of a large data context represents a significant performance constraint. As a result, instead of using a single vast data context, you should break the data context into numerous smaller data contexts.
Ideally, you should only have one data context per module or unit of work. To use multiple data contexts, simply create a new class for each data context and extend it from the DbContext class.
Disable lazy loading
Lazy loading is a feature that eliminates the need to load unnecessary related entities (as in explicit loading) and seems to remove the developer from dealing with related entities entirely. Because EF Core is adept at automatically loading related entities from the database when accessed by your code, lazy loading seems like a nice feature.
However, lazy loading is especially prone to generating unnecessary additional round trips, which could slow down your application. You can turn off lazy loading by specifying the following in your data context:
ChangeTracker.LazyLoadingEnabled = false;
Use DbContext pooling
An application typically has multiple data contexts. Because DbContext objects may be costly to create and dispose of, EF Core offers a mechanism for pooling them. By pooling, DbContext objects are created once, then reused when needed.
Using a DbContext pool in EF Core can improve performance by reducing the overhead involved in building and disposing of DbContext objects. Your application may also use less memory as a result.
The following code snippet illustrates how you can configure DbContext pooling in the Program.cs file.
builder.Services.AddDbContextPool<MyDbContext>(options => options.UseSqlServer(connection));
This article provided a discussion of best practices that can be adopted to improve data access performance in EF Core. Of course, every application has different data access requirements and characteristics. You should benchmark your EF Core performance before and after you apply these changes to assess the results for your specific application. An excellent tool for the task is BenchmarkDotNet, which you can read about in a previous post.
Copyright © 2022 IDG Communications, Inc.