Optimize your use of Entity Framework by following best practices that enhance performance and maintainability. These guidelines cover everything from efficient querying and data manipulation to leveraging migrations and in-memory providers, ensuring your application runs smoothly and effectively.
Entity Framework allows you to provide a strongly typed object framework (ORM) over your database. This helps abstract the database away allowing it to be replaced with other technologies as needed.
It's expensive retrieving data from a database, as such it's important to only ask for the rows you require when getting data.
When you cast IQueryable to IEnumerable and then query the data from there, Entity Framework must collect the data at the point you do the cast. This can result in very significant extra database load, and extra processing on the client side.
NOTE: Using .AsEnumerable() achieves the same effect.
One of EF Core's best features is the fact it tracks any changes you make to entities after you retrieve them. However this comes with a cost, if the data is only being read and the returned entities will never be modified then you can use the AsNoTracking method to inform EF Core not to bother tracking changes.
This results in fairly significant memory and CPU improvements on the client side.
When retrieving data it's much more efficient to only collect the data you need. It saves computation and IO on the database and also saves memory and CPU on the calling side.
The Update method on an entity in EF Core marks all of its fields as dirty. This will result in all the fields being written back to the database.
Often developers will include all the related entities in a query to help with debugging. Always remember to take these out. They cause excessive database load.
Pagination can be expensive if all the pages are retrieved from the database before grabbing the relevant page. It's much more efficient to get only the page number requested back from the database.
TagWith adds comments to the generated SQL. This makes it easier to identify queries when they run on the database.
To avoid embarrassing failures in Production, it is important to ensure that your development systems are as similar as possible to what's expected in Production.
Benchmarking your system's performance is important. This is making sure you have information on how your system performs with a known set of data and load.
There are plenty of good benchmarking tools for .Net solutions.
Databases are slow at doing bulk updates. It's generally significantly faster to break bulk processing down into manageable chunks. It also avoids other database users experiencing significant blocking issues.
Raw SQL comes with risks but sometimes it is the best solution.
Most enterprise applications require a database to store data. Once you have a database you need a way to manage the schema.
Entity Framework Code First Migrations allow you to update a database schema rather than recreate it from scratch. This is useful when you have a production database that you want to keep, but you want to make changes to the schema.
When testing code that depends on Entity Framework Core, the challenge often lies in how to effectively mock out the database access. This is crucial for focusing tests on the functionality surrounding the DB access rather than the database interactions themselves. The EF Core In-Memory provider is a tool designed to address this need.
Some older projects .NET Framework project will have EDMX instead of modern DbContext first introduced in Entity Framework 4.1, which first introduced DbContext and Code-First approach back in 2012, replacing the ObjectContext that EDMX used for Database-First approach.
In this rule, we’ll use ObjectContext and Entities interchangeably. ObjectContext is the base class that is used by the generated class, which will generally end with Entities (e.g. DataEntities).
The rule is focusing on .NET 8+ as the support for .NET Framework projects and Nuget was added back, which makes a staged migration a lot more feasible. Most, if not all, are still applicable for .NET 7 as well.
There are a few strategies regarding the migration from a full rewrite with to a more in-place migration. Depending on the scale and complexity of the project. This rule will describe an approach that balances the code we need to rewrite and modernisation.
The focus is to minimise the amount of time no deployments are made due to migration.
The strategy in this rules will include:
ObjectContext/Entities class with a custom IDbContext interface (e.g. ITenantDbContext)DbContext.OnConfiguringObjectSet<T> with DbSet<T>.AsEnumerable(), use raw SQL or change how the query worksSystem.Data.Entity namespace in all files using EF Core 3.1 (otherwise, you'll get odd Linq exceptions)Microsoft.EntityFrameworkCore namespace.AddDbContext() or .AddDbContextPool()Steps 6 and 7 are required when upgrading from .NET Framework to .NET 8 and the solution is too complex to do the migration in one go. For simple projects, if EDMX is the only major blocking issue, they should go straight to .NET 8 and EF Core 8.
NOTE: With some smart abstraction strategies, it is possible to do steps 3 - 5 while still having a working application. It is only recommended for experienced developers in architecture and how EF operates to avoid bugs related to running 2 EF tracking systems. This will impact EF internal caching and saving changes.
In this rule, we'll only cover abstracting access to ObjectContext with a custom IDbContext and how to scaffold the DB. The rest of the steps require in-depth code review and may differ greatly between projects.
Before starting, it’s important to note that ObjectContext and EDMX are no longer supported and we need to do a full rewrite of the data layer. You can wrap ObjectContext with an interface that looks like modern DbContext, as most commonly used methods are identical.
The wrapper below not only allows us to use ObjectContext in a cleaner way (see Rules to Better Clean Architecture) but also allows us to better manage the differences between ObjectContext and DbContext without needing to refactor the business logic.
using System.Data.Entity.Core.Objects;public interface ITenantDbContext{ObjectSet<Client> Clients { get; }int SaveChanges();Task<int> SaveChangesAsync(CancellationToken ct = default);}/// <summary>/// Implement DbContext as internal, so that external libraries cannot access it directly./// Expose functionality via interfaces instead./// </summary>internal class TenantDbContext : ITenantDbContext{private readonly DataEntities _entities;public TenantDbContext(DataEntities entities){_entities = entities;}public ObjectSet<Client> Clients => _entities.Clients;public int SaveChanges() => _entities.SaveChanges();public Task<int> SaveChangesAsync(CancellationToken ct = default) => _entities.SaveChangesAsync(ct);}
✅ Figure: Abstracting ObjectEntities behind an interface and using an interface to reduce the amount of issues while migrating.
NOTE: The changes made in this section are still compatible with .NET Framework, allowing us to deliver value to the clients while the above changes are made.
Now that we abstracted access to the data, it's time to scaffold the DB. The easiest way to do this is by using EF Core Power Tools.
Figure: Select reverse engineer tool
Figure: Data Connection
Figure: Database Objects
Figure: Settings for project
Persistence folderFigure: Settings for project
DbContext class will be auto-generated by EF Core Power ToolsFigure: Settings for project
EF Core 3.1 EDMX - Walk-through: Using an Entity Framework 6 EDMX file with .NET Core | ErikEJ's blog
While the above blog is supposedly working in EF Core 3.1, there is no information on whether that is true for .NET 8. It would still require a lot of migrations.
Limitations: