I want to share essential knowledge about EntityFramework (EF), which should be enough to implement most of the trivial tasks working with the relational database. This article should help young developers to understand EF better and avoid some mistakes, which I did on my way.
Firstly let’s prepare a database. It reflects the most trivial case with the post and Author. I decided not to invent the wheel because this article reveals the primary usage of EF and not about some concrete model.
Let`s take a look at database schema:
-- script to create database and required tables
In the project, we need to include proper NuGet packages:
dotnet add package Microsoft.EntityFrameworkCore -v 6.0.0
Besides the EF package, we should include specific NuGet for proper database dialect. We used the Npgsql package and Postgres as a database for examples in this article:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL -v 6.0.0
We are ready to use EF in our project, and it’s time to configure POCO classes that will represent database objects in the .NET world:
public class Author
As you can see, classes represent almost the same structure as a table in the database. A difference comes with virtual properties. In the case of Author, it’s a list of Posts, and accordingly, the Post entity has virtual Author property. Such properties, also called Navigation properties, allow us to work with flat tables in the objects world.
We need to provide proper configuration for EF to make it understand navigation properties. Also, the Post table has a serial type in the database, and we need to tell EF about this. Furthermore, we should properly map all properties to columns.
I want to share the knowledge that EF gives us a way to provide a mapping between objects and tables in a few different ways. I want to leave entity objects simple as they are right now and not overwhelm them with many attributes. Fluent API provided by EF gives such ability. When you try to make it more straightforward and more visible for the future developer, it’s better to separate DB mappings in different scoped files. Another good point is to make classes for mapping internal and prevent access to them from other projects in the solution.
internal class AuthorMap
We defined what property is the primary key for the table and mapped properties accordingly in such mapping. In the end, we described a navigation property and helped EF to understand how entities related to each other.
note: we’ll use this mapping file a bit later
internal class PostMap
Our mapping for Posts looks similar to authors, and you can notice a sequence name and some code related to it. It comes from PostgreSQL, and if you use another database server, you need to provide another configuration for the autoincremented column.
And finally, we can create our database context:
public class ExampleDbContext : DbContext
What you should avoid during EF configuration:
- selecting name for your .net entities adhere DB table name and don’t use synonyms for this
- previous rule is also applicable for navigation properties
- try to separate DB mapping from each other it helps read/review code later
We need to verify that it works and the best way to do it is to start using it.
Note: We should asynchronously implement all IO-bound operations; EF provides asynchronous analog to the most supported API.
public static async Task AddEntityToDatabaseAsync()
Under the hood of AddAsync, EF does something similar to the last approach, and it prepares a set for the proper entity type and attaches the entity with status Added. ChangeTracker tracks these changes, and when you call SaveChangesAsync, it sends all data to the database.
Note: if you are working with web servers, try to save changes at the end of the HTTP request. It helps to protect data integrity and prevent an excess round trip to the database.
public static async Task GetEntitiesFromDatabaseAsync()
As you can see, when you know the key, you can extract the entity from the database in a few ways. In most cases, FirstOrDefaultAsync helps but be aware of tracking settings. If you don’t mark a request to database AsNoTracking or set it up on context level, EF puts your entity to ChangeTracker. It requires additional resources from the system for reading flow; it’s unnecessary.
Note: Always use AsNotracking for reading operations if you are not going to modify extracted entities later
By default, when you load entity EF doesn’t load navigation properties to memory. When you access the navigation property, the possibility to get NullReferenceException is high. It is pretty convenient to load entity together with navigation properties just once and use it for in-memory business logic operations.
Note: Be aware of lazy loading. It can cause performance problems that are hard to identify.
public static async Task LoadNavigationPropertyAsync(ExampleDbContext context, int postId)
As you see, we can use Include to specify which navigation properties we want to load from the database.
EF is quite clever in understanding the relationship between objects and providing proper values for foreign keys.
public static async Task ExampleOfNavigationInsertAsync(ExampleDbContext context)
As you can see, we can omit AuthorId and EF provides it for us.
Another proper technique that you can notice here is wrapping modified flow into transaction scope. In case of a problem, we can roll back the transaction, and data in the database stays not corrupted.
Note: Working with web servers, try to wrap calls that should modify data into transactions and prevent any corrupted data from being inserted into the database.
EF as ORM simplifies life and works with databases smoothly. In this article, you can find base examples of configuring and working with EF. Some examples contain note which helps you to improve your day-to-day interactions with EF. I will try to prepare all the best practices on using EF in some next articles.