ASP.NET Core 2.2 REST API #8 — Managing Entities with Entity Framework Core
Up Next: What is a JWT and why your REST API needs it
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 DataContext
and 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 Post
‘s Id
property.
Integrating EF Core
First, we need to inject a DataContext
object in our service.
Then, change our CRUD methods:
GetPosts
returnsawait _dataContext.Posts.ToListAsync()
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 T
to Task<T>
and also add the async
keyword.
We have to do the same transformations for everything:
GetPostById
returnsawait _dataContext.Posts.SingleOrDefaultAsync(x => x.Id == postId)
UpdatePost
is a bit trickier. We can just doawait _dataContext.Posts.Update(postToUpdate)
. This queues the object to be saved when we callawait _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 returnresult == 1
in order to signify wether the operation was successful.DeletePost
combines all the previous stuff: First, retrieve the post throughGetPostById
, then schedule it for deletion through_dataContext.Posts.Remove(post)
, finally calling saving changes and returning proper status.Create
is just_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 Controller
:
- Make every method be awaitable (
async Task<IActionResult>
) - Change all
_postService
invocations to use theawait
keyword - Since our
CreatePost
no 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 theName
property.
Action
There are no more things we need to do in terms of code. Just
- Make sure you are using the correct
ConnectionString
in the app settings JSON - Open up the package manager console and run the
Migrations
(Command isAdd-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 Update-Database
command.
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.
Code is available on Github and the instructional videos are located on YouTube.
Keep Coding