ASP.NET Core 2.2 REST API #8 — Managing Entities with Entity Framework Core
Until now, we have only been using an in-memory storage service for our service. We want to have some sort of persistent storage in our application. We are going to use Entity Framework in combination with SQL Server in order to accomplish this task.
What is Entity Framework?
It’s basically a way to write C# instead of SQL, allowing us to even use objects to perform any database operation we want. For now, we are going to make our
PostService use EF Core as a backend for storing and managing data.
Recall that in the first episode of this series, when we created the project, we selected
Identity . This selection generated the
Migrations by default. Basically this enabled EF Core and SQL Server by default.
For simplicity we are going to use our domain object as an entity in the database. In the future, if our domain object starts to look different than what is saved in the database, we are going to refactor this.
The first step is to add a
DbSet<Post> in our data context. A
DbSet<T> in EF, corresponds to a
Table in database language. If we run the migrations right now, a table will be created. We also have control over the table name, by using
[TableName("name_we_want")] but this is not needed; the default name would be
posts and it is fine.
A table can not exist without a
Primary Key , so in order to do that, we are going to add the
[Key] attribute to our
Integrating EF Core
First, we need to inject a
DataContext object in our service.
Then, change our CRUD methods:
Keep in mind that EF Core supports Async operations through it’s extension methods. Each operation is an actual disk IO operation and by leveraging the await/async signatures we are going to take advantage of this. We encourage you to research more about this topic. This is why we also need to change our method’s return type from
Task<T> and also add the
We have to do the same transformations for everything:
await _dataContext.Posts.SingleOrDefaultAsync(x => x.Id == postId)
UpdatePostis a bit trickier. We can just do
await _dataContext.Posts.Update(postToUpdate). This queues the object to be saved when we call
await _dataContext.SaveChangesAsync()in order to improve IO operation efficiency through batching. We don’t need to perform any other operations, so we can just call this to retrieve an integer that returns how many items got updated. Then, we can just return
result == 1in order to signify wether the operation was successful.
DeletePostcombines all the previous stuff: First, retrieve the post through
GetPostById, then schedule it for deletion through
_dataContext.Posts.Remove(post), finally calling saving changes and returning proper status.
_dataContext.Posts.AddAsync()and then, saving changes, returning proper status.
We need to change the interface to use
async Task<T> as well. A best practise for code readability is to add the
-Async suffix (exactly as EF does) to our methods if they support async operations.
The default data context is registered as a
Scoped service, while our
PostService is registered as a singleton. You can’t use a scoped service through a singleton one — this is why we are going to change our post service registration to be scoped. What scoped really means is that the lifetime of each object is going to equal the lifetime of each request (each request gets a different instance).
As a last step, is to update our
- Make every method be awaitable (
- Change all
_postServiceinvocations to use the
- Since our
CreatePostno longer needs an id, because it will be auto generated as a primary key from EF Core. We can remove the id from the contract whilst keeping the
There are no more things we need to do in terms of code. Just
- Make sure you are using the correct
ConnectionStringin the app settings JSON
- Open up the package manager console and run the
Add-Migration AddPosts). This notifies our database that things actually changed.
Migrations are only a C# thing and are auto-generated for us.
You can see that it adds a new posts table with the primary key constraint we added as an attribute. There is another migration being generated for the users schema from the first part.
Just creating a migration does not mean that it executes. If you open up your favourite database interaction toolkit, you can actually see that there are no data inside. To run them you have to use the
It’s going to generate some scripts and execute them. You can see that there are many tables created. First our posts table, exactly the way we described it from C# code:
There are more tables created, like the
AspNetUser ones for Identity. A honourable mention is the migration history table which records migration status so it can re-apply or skip migrations to achieve some target state.
That’s good for now. If you mess around with the API via the Swagger UI, save some posts and then restart the API or look into the database, the posts are persisted, without writing a single line of SQL code.