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.

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.

First, we need to inject a DataContext object in our service.

Then, change our CRUD methods:

  • GetPosts returns await _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 returns await _dataContext.Posts.SingleOrDefaultAsync(x => x.Id == postId)
  • UpdatePost is 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 == 1 in order to signify wether the operation was successful.
  • DeletePost combines 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.
  • 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 the await 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 the Name 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 is 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 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.

Up Next: What is a JWT and why your REST API needs it

Code is available on Github and the instructional videos are located on YouTube.

Keep Coding