Entity Framework Core In Docker Container - Part I - SQL Server

In this article, we will learn using Docker with Entity Framework Core and with different databases.

I have searched for simple tutorials on how to use Docker with Entity Framework Core with different databases and did try several tutorials and code examples but none of those satisfied me completely. The existing GitHub and Google tutorials are either outdated or not working properly. For that reason, I have written this article. In this article, I shall show you how to create an Entity Framework Core Containerized App with SQL Server 2017 within a Docker container from scratch, and I shall try to use as little code as possible. To make it more interesting, I shall show you how you can enable the Query Store in SQL Server container from the SQL Server Management Studio (SSMS). The SQL Server Query Store allows the analysis of query performance from within the container. In the next article, I shall handle an embedded database like SQLite in the same way. In addition, I shall explain to you how you can open and profile the SQLite database from a Docker container with Db Browser (SQLite). 

In my example, I have two Microservices and Products WebAPI. It has one entity “Product”. This service uses SQL Server container as a backend to persist the products. The other service Orders WebAPI (Part II) also has one entity “Order” and it uses the SQLite to persist the orders. The main difference between Product service and the Order service is that the Order service contains an embedded database (in-process database) within the container itself.

I am using one database pro service technique, i.e., we have two databases ( ProductsDb and OrdersDb).
 
ProductsWebApi contains only the connection information to the database, and in this case, the SQL Server Container. Additionally, it is linked to the SQL Server container as shown in the image -1- below.
 
OrdersWebApi (Part II) is generating the database when the service startup.
 
Entity Framework Core with SQL Server in Docker Container
Image -1- Databases in the containers 

If you have Docker in your system, then you can skip this section.

Docker Installation

You have to download and install the Docker from the following link.

https://www.docker.com/products/docker-desktop

I have downloaded Docker-Desktop application and installed everything as default. One thing I have changed after the installation is that I have selected the checkbox with drive ‘C’, as shown in the image below.

Entity Framework Core with SQL Server in Docker Container
Image -2- Select C driver as Shared Drives
 
Note: I have logged into Docker account, please login with your docker account credentials.
 
 Entity Framework Core with SQL Server in Docker Container
Image -3- my Docker account 

ProductsWebApi: Let us do it together (step by step)

I have started Visual Studio 2019 and selected "Create New Project", and then I have chosen ASP.NET Core Web Application, as you can see in the image.

Entity Framework Core with SQL Server in Docker Container
Image -4- Create a new project in Visual Studio 2019 

I have set the project name to “ProductsSqlServer”.

Entity Framework Core with SQL Server in Docker Container
Image -5- Set project name in Visual Studio 2019 

A dummy service is generated. Press “F5” to build and run the project.

Entity Framework Core with SQL Server in Docker Container
Image -6- The generated dummy project.
 
Entity Framework Core with SQL Server in Docker Container
Image -7- execute a dummy service 

[“value1”, “value2”] is hardcoded in the ValuesController.cs.

We can begin with the work. First, we need to build our domain model.

Database context

  1. Creating and adding the entity “Product” to the project.
  2. Creating and adding ProductsDBContext to the project and set up the connection string.
  3. Creating and adding ProductsController to the project with some logic to handle the requests.

 Here is the Product entity,

  1. Public class Product {  
  2.     /// <summary>  
  3.     /// Gets or sets the identifier.  
  4.     /// </summary>  
  5.     /// <value>The identifier.</value>  
  6.     public int Id {  
  7.         get;  
  8.         set;  
  9.     }  
  10.   
  11.     /// <summary>  
  12.     /// Gets or sets the name.  
  13.     /// </summary>  
  14.     /// <value>The name.</value>  
  15.     public string Name {  
  16.         get;  
  17.         set;  
  18.     }  
  19.   
  20.     /// <summary>  
  21.     /// Gets or sets the price.  
  22.     /// </summary>  
  23.     /// <value>The price.</value>  
  24.     public decimal Price {  
  25.         get;  
  26.         set;  
  27.     }  
  28.   
  29.     /// <summary>  
  30.     /// Gets or sets the description.  
  31.     /// </summary>  
  32.     /// <value>The description.</value>  
  33.     public int Description {  
  34.         get;  
  35.         set;  
  36.     }  
  37. }  

Here is the Products database context.

  1. public class EntityFrameworkSqlServerContext: DbContext {  
  2.     /// <summary>  
  3.     /// Initializes a new instance of the <see cref="EntityFrameworkSqlServerContext"/> class.  
  4.     /// </summary>  
  5.     /// <param name="options">The options.</param>  
  6.     public EntityFrameworkSqlServerContext(DbContextOptions < EntityFrameworkSqlServerContext > options): base(options) {  
  7.         // Creates the database !! Just for DEMO !! in production code you have to handle it differently!  
  8.         this.Database.EnsureCreated();  
  9.     }  
  10.   
  11.     /// <summary>  
  12.     /// Gets or sets the products.  
  13.     /// </summary>  
  14.     /// <value>The products.</value>  
  15.     public DbSet < Product > Products {  
  16.         get;  
  17.         set;  
  18.     }  
  19. }  

Moreover, I have added the connection string to the appsettings.json.

  1. {  
  2.     "ConnectionStrings": {  
  3.         "SqlConnection""Server=sqlserver;Database=ProductsDb;User Id=sa;Password=BigPassw0rd"  
  4.     },  
  5.     "Logging": {  
  6.         "LogLevel": {  
  7.             "Default""Warning"  
  8.         }  
  9.     },  
  10.     "AllowedHosts""*"  
  11. }  
Note
The hostname “sqlserver” does not exist in my machine or network.
 
Here is the Product controller with some logic to store; load deletes the products.  
  1. [ApiController, Route("api/[controller]")]  
  2. public class ProductsController: ControllerBase {  
  3.     /// <summary>The products database context</summary>  
  4.     private readonly EntityFrameworkSqlServerContext _productsDbContext;  
  5.   
  6.     public ProductsController(EntityFrameworkSqlServerContext productsDbContext) {  
  7.         _productsDbContext = productsDbContext;  
  8.     }  
  9.   
  10.     /// <summary>Gets the product.</summary>  
  11.     /// <returns>Task<ActionResult<IEnumerable<Product>>>.</returns>  
  12.     /// <remarks> GET api/values</remarks>  
  13.     [HttpGet]  
  14.     public async Task < ActionResult < IEnumerable < Product >>> GetProduct() {  
  15.         return Ok(await _productsDbContext.Products.ToListAsync());  
  16.     }  
  17.   
  18.     /// <summary>Creates the specified product.</summary>  
  19.     /// <param name="product">The product.</param>  
  20.     /// <returns>Task<ActionResult<Product>>.</returns>  
  21.     [HttpPost]  
  22.     public async Task < ActionResult < Product >> Create([FromBody] Product product) {  
  23.         if (!ModelState.IsValid)  
  24.             return BadRequest(ModelState);  
  25.   
  26.         await _productsDbContext.Products.AddAsync(product);  
  27.         await _productsDbContext.SaveChangesAsync();  
  28.   
  29.         return Ok(product);  
  30.     }  
  31.   
  32.     /// <summary>Updates the specified identifier.</summary>  
  33.     /// <param name="id">The identifier.</param>  
  34.     /// <param name="productFromJson">The product from json.</param>  
  35.     /// <returns>Task<ActionResult<Product>>.</returns>  
  36.     [HttpPut("{id}")]  
  37.     public async Task < ActionResult < Product >> Update(int id, [FromBody] Product productFromJson) {  
  38.         if (!ModelState.IsValid)  
  39.             return BadRequest(ModelState);  
  40.   
  41.         var product = await _productsDbContext.Products.FindAsync(id);  
  42.   
  43.         if (product == null) {  
  44.             return NotFound();  
  45.         }  
  46.   
  47.         product.Name = productFromJson.Name;  
  48.         product.Price = productFromJson.Price;  
  49.         product.Description = productFromJson.Description;  
  50.   
  51.         await _productsDbContext.SaveChangesAsync();  
  52.   
  53.         return Ok(product);  
  54.     }  
  55.   
  56.     /// <summary>Deletes the specified identifier.</summary>  
  57.     /// <param name="id">The identifier.</param>  
  58.     /// <returns>Task<ActionResult<Product>>.</returns>  
  59.     [HttpDelete("{id}")]  
  60.     public async Task < ActionResult < Product >> Delete(int id) {  
  61.         var product = await _productsDbContext.Products.FindAsync(id);  
  62.   
  63.         if (product == null) {  
  64.             return NotFound();  
  65.         }  
  66.   
  67.         _productsDbContext.Remove(product);  
  68.         await _productsDbContext.SaveChangesAsync();  
  69.   
  70.         return Ok(product);  
  71.     }  
  72. }  
And I have registered the database context in the startup class as below.
  1. public void ConfigureServices(IServiceCollection services) {  
  2.     services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);  
  3.     services.AddDbContext < EntityFrameworkSqlServerContext > (options => options.UseSqlServer(Configuration.GetConnectionString("SqlConnection")));  
  4. }  
  5.   
  6. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  7. public void Configure(IApplicationBuilder app, IHostingEnvironment env) {  
  8.     if (env.IsDevelopment()) {  
  9.         app.UseDeveloperExceptionPage();  
  10.     }  
  11.     app.UseMvc();  
  12. }   

The product service is done, but you cannot use it or test it because of missing back-end. Thus, we need to create two containers in Docker: One for product service and the other container for SQL Server. To do that, you need to follow the below-described steps.

In Visual Studio 2019, I have selected the project menu item “ProdcutsSqlServer” and then I have selected “Add” and then “Container Orchestration Support”.
 
Entity Framework Core with SQL Server in Docker Container
Image -8- Adding “Container Orchestration Support”
 
Entity Framework Core with SQL Server in Docker Container
 
I have selected Docker Compose and clicked on the “OK” button.
 
Entity Framework Core with SQL Server in Docker Container
 
I have clicked the OK button. 

Below, you can see the generated/regenerated Dockerfile inside the project, which is related to the same service. Moreover, you can see docker-compose.yml, which is shared for multiple services. 

Entity Framework Core with SQL Server in Docker Container 

To avoid any certification problem, I will remove the SSL connection from everywhere and I have removed the 80, 443 from the Dockerfile so that my Dockerfile and docker- compose.yml files look like below.

Dockerfile

  1. FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base  
  2. WORKDIR /app  
  3.       
  4. FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build  
  5. WORKDIR /src  
  6. COPY ["ProductsSqlServer/ProductsSqlServer.csproj""ProductsSqlServer/"]  
  7. RUN dotnet restore "ProductsSqlServer/ProductsSqlServer.csproj"  
  8. COPY . .  
  9. WORKDIR "/src/ProductsSqlServer"  
  10. RUN dotnet build "ProductsSqlServer.csproj" -c Release -o /app  
  11.   
  12. FROM build AS publish  
  13. RUN dotnet publish "ProductsSqlServer.csproj" -c Release -o /app  
  14.   
  15. FROM base AS final  
  16. WORKDIR /app  
  17. COPY --from=publish /app .  
  18. ENTRYPOINT ["dotnet""ProductsSqlServer.dll"]  
docker- compose.yml
  1. version: '3.4'  
  2.   
  3. services:  
  4.   productssqlserver:  
  5.     image: ${DOCKER_REGISTRY-}productssqlserver  
  6.     build:  
  7.       context: .  
  8.       dockerfile: ProductsSqlServer/Dockerfile  
  9.   
  10. docker-compose-override.yml  
  11. version: '3.4'  
  12.   
  13. services:  
  14.   productssqlserver:  
  15.     environment:  
  16.       - ASPNETCORE_ENVIRONMENT=Development  
  17.     ports:  
  18.       - "49630:80"  
After adding the link to the SQL Server container, the docker-compose.yml will be like this.
  1. version: '3.4'  
  2.   
  3. services:  
  4.   productssqlserver:  
  5.     links:  
  6.       - sqlserverService  
  7.     image: ${DOCKER_REGISTRY-}productssqlserver  
  8.     build:  
  9.       context: .  
  10.       dockerfile: ProductsSqlServer/Dockerfile  
  11.   
  12.   sqlserverService:  
  13.     image: microsoft/mssql-server-linux:2017-latest  
  14.     hostname: 'sqlserver'  
  15.     environment:  
  16.       ACCEPT_EULA: Y  
  17.       SA_PASSWORD: "BigPassw0rd"  
  18.     volumes:  
  19.       - ./data/mssql:/var/opt/mssql3  
  20.     ports:  
  21.       - '1433:1433'  
  22.     expose:  
  23.       - 1433  
Remember two things - the hostname is 'sqlserver' that we have used in the connection string; and we need to expose the port -1433 so that we can connect to the SQL Server from the SQL Server Management Studio (SSMS).

Press Alt+Enter on “docker-compose” project and set the Service URL as shown below.

Entity Framework Core with SQL Server in Docker Container
Image -9- browser start-up link 

The project is now ready to run. So, I have pressed “F5” and finally, the browser starts with empty values.

Entity Framework Core with SQL Server in Docker Container
Image -10- start-up the browser with empty values 

However, we do not have any data (I did not seed the database). Here, I will use the tool Postman (https://www.getpostman.com) to feed the database with some data. 

I have started the Postman, and I have sent the following product as a JSON string.

!!Do it by yourself!!

  1. HTTP:    http://localhost:49630/api/products  
  2. JSON:   {"name""6-Pack Beer""price""400""description""Console Game"}  
Entity Framework Core with SQL Server in Docker Container
Image -11- Sending a JSON string 

I am back to the browser, and I have refreshed it with “F5”. 

Entity Framework Core with SQL Server in Docker Container

It works perfectly. 

Let us take a look into the database within the container. 

I have opened SQL Server Management Studio (SSMS), see an image -12-. 

Entity Framework Core with SQL Server in Docker Container
Image -12- starting SSMS 

Server name: localhost, 1433

Login: sa

Password: BigPassw0rd 

I have browsed the Products table. It is very impressive! I can see everything from my local machine, see image below. 

Entity Framework Core with SQL Server in Docker Container 

I can as well enable the Query Store inside the SQL Server container. 

Entity Framework Core with SQL Server in Docker Container
 
Fantastic, let us perform a pressure test.
 
I will send the same JSON string {"name": "6-Pack Beer", "price": "400", "description": "Console Game"} 10000 times with the help of Postman.  

More information

https://learning.getpostman.com/docs/postman/collection_runs/intro_to_collection_runs/ 

Entity Framework Core with SQL Server in Docker Container
 
It took some time until it has been finished on my machine.
 
Entity Framework Core with SQL Server in Docker Container
 
Let us take a look into the generated plans and charts! 
 
Entity Framework Core with SQL Server in Docker Container
 
Awesome! I can see everything!
 
And here is our insert query, see image -13-
 
Entity Framework Core with SQL Server in Docker Container
Image -13– Insert Product Query
 

Summary

Docker and SQL Server work very well together. You can easily connect to SQL Server, and it is easy to optimize the performance or to profile the database. During my tests, I have seen some problems, for example, sometimes I have to restart the Docker application, and sometimes I have to delete the vs. directory from the solution directory.

The real problem is that you need a lot of time to get the Containerized App working correctly. My golden tip from this article is: “This kind of services software architecture is really powerful, but it is also complicated for developers, and it needs a lot of time and knowledge. If you do not need the horizontal scalability, then you have to think before using Containerized App”. In the next article, I shall go further with the Docker and SQLite example, in this case, we shall see that the database is embedded within the service. I hope that you have got my message from this article. Finally, I would like to thank you for reading the article until the end, and you can find the source code on GitHub: Entity Framework Core Docker