【C#】SQL Serverと.NETのAPIをDockerで動かす方法
はじめに
.NETのAPIと、そのデータベースを、すべてDockerで動かす方法を載せておきます。
公式だと、.NETのアプリをDockerでやる方法はチュートリアルで載っていましたが、APIやSQL Serverは見当たりませんでした。
そのため、ハンズオン形式で、まとめておきます。
- APIとDockerを一度試してみてからSQL serverをやってみたい方
「API(Sample)をDockerで動かす」からがオススメ - お急ぎの方/早くAPIとDocker,SQL Serverをつなげてみたい方
「API、SQL ServerをDockerで動かす」からがオススメ
準備
WSLのインストール:公式サイト
VSCodeのインストール:公式サイト,C#と.NETの環境構築方法 -WindowsとWSL-【VSCode】
Dockerのインストール:WSL 2 での Docker リモート コンテナーの概要 - Microsoft Learn
Dotnetのインストール:C#と.NETの環境構築方法 -WindowsとWSL-【VSCode】
筆者の環境
- WSL
NAME STATE VERSION
* Ubuntu Running 2
(Powershellでwsl -l -v
で確認)
- Dockerのバージョン
Docker version 24.0.7, build afdd53b
(docker --version
で確認)
- dotnet
8.0.105
(WSLでdotnet --version
で確認)
API(Sample)をDockerで動かす
SQL ServerとAPIを同時に実行する前に、まずAPIのみでDockerで実行できるかやってみましょう。
APIプロジェクトを作る
- 以下を実行する
dotnet-api-sampleディレクトリを作り、ディレクトリに移動します。
mkdir dotnet-api-sample
cd dotnet-api-sample
- dotnet newコマンドでWeb APIを作成
dotnet new webapi
dotnet newコマンドについて
- VSCodeを開く
code .
- 以下でAPIを実行確認
dotnet run
dotnet runコマンドについて
dotnet run -- 明示的なコンパイルや起動コマンドなしで、ソースコードを実行します。
dotnet runを参照
http://localhost:XXXX/weatherforecast
で実行確認できます。
XXXXはポートを入力してください。
Dockerfileを自動作成
- Ctrl + Shift + P
- Docker: Add Docker Files to Workspace... を選択
- 項目を選択していき、Dockerfileを作成
Dockerイメージをビルド
docker build -t <任意のイメージ名> -f Dockerfile .
記述例の説明
仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のp180ページより引用↓
docker build -t 作成するイメージ名 材料フォルダのパス
以下で、Dockerのイメージ一覧を確認出来ます。
docker images
コンテナを実行
- 以下を実行する
docker run --rm -p 5000:<リッスンするポート> <任意のイメージ名>
docker runとは
仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん P81より引用
Dockerイメージをダウンロードし、コンテナを作成して起動する(ダウンロードは必要な場合のみ)。
docker image pull,docker container create,docker containaer startの一連の動作をひとまとめにしたもの
リッスンするポートは、Dockerfile内の以下のXXXXの部分です。
EXPOSE XXXX
のXXXXの部分がリッスンするポートになっているはずです。
EXPOSEとは
仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のp181ページより引用↓
- EXPOSE:通信を想定するポートをイメージの利用者に伝える
-
動作確認する
http://localhost:5000/weatherforecast
にアクセスしてみます。
dotnet run
のときと同じように見えていればOKです。 -
動作の停止
ctrl + c
を押せば、動作が停止し、コンテナが削除されます。
参考サイト
API、SQL ServerをDockerで動かす
APIがDockerで動かせることを確認したところで、実際にAPIとSQL ServerをDockerで動かす手順をやっていきます。
完成したAPIは、以下のGitHubにて確認できます。
プロジェクト作成
- 以下を実行する
dotnet-api-sampleディレクトリを作り、ディレクトリに移動します。
mkdir dotnet-api-sample
cd dotnet-api-sample
- dotnet newコマンドでWeb APIを作成
dotnet new webapi
- VSCodeを開く
code .
- 以下でAPIを実行確認
dotnet run
ファイル構成を確認
📁 dotnet-api-sample
└──📂 bin
└──📂 obj
└──📂 Properties
├── appsettings.Development.json
├── appsettings.json
├── dotnet-api-sample.csproj
├── dotnet-api-sample.http
├── dotnet-api-sample.sln
└── Program.cs
gitで管理したい場合の手順
- 以下のコマンドでgitignoreを作成
dotnet new gitignore
- 以下のコマンドでgitを使えるように。
git init
- 以下でブランチをMasterからmainに変更
git branch -m main
ここで、git add --all
とgit commit -m "Initial Commit"
しておくと良いと思います。
クラスを作成
- Entitiesフォルダーを作り、Bookクラスを作成
※参考サイトとは違い、namespaceで囲わない書き方にしています。
また、Warningが出るため、requiredを追加しています。
namespace SimpleWebApi.Entities;
public class Book
{
public int BookId { get; set; }
public required string Title { get; set; }
public required string Author { get; set; }
}
- Dataフォルダーを作り、BookContextクラスを作成
※参考サイトとは違い、namespaceで囲わない書き方にしています。
using Microsoft.EntityFrameworkCore;
using SimpleWebApi.Entities;
namespace SimpleWebApi.Data;
public class BookContext : DbContext
{
public BookContext(DbContextOptions options) : base(options)
{
}
public DbSet<Book> Books { get; set; }
}
- Controllersフォルダーを作り、BooksControllerクラスを作成
※参考サイトとは違い、namespaceで囲わない書き方にしています。
using Microsoft.AspNetCore.Mvc;
using SimpleWebApi.Data;
using SimpleWebApi.Entities;
namespace SimpleWebApi.Controllers;
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
private readonly BookContext context;
public BooksController(BookContext context)
{
this.context = context;
}
[HttpGet]
public ActionResult<IEnumerable<Book>> List()
{
var books = context.Books;
return books;
}
}
ファイル構成を確認
📁 dotnet-api-sample
├──📂 bin
├──📂 obj
├──📂 Properties
├──📂 Controllers
│ └── BooksControllers.cs
├──📂 Data
│ └── BookContext.cs
├──📂 Entities
│ └── Book.cs
├── appsettings.Development.json
├── appsettings.json
├── dotnet-api-sample.csproj
├── dotnet-api-sample.http
├── dotnet-api-sample.sln
└── Program.cs
必要なパッケージをインストール
- 以下のコマンドを実行して、EntityFrameworkCoreをインストール
dotnet add package Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCoreとは
Entity Framework Core(EFコア)は、SQL Server(オンプレミスおよびAzure)、SQLite、MySQL、PostgreSQL、Oracle、Azure Cosmos DBなど、さまざまなデータベースに対して、クリーンでポータブルな高レベルのデータアクセスレイヤーを.NET(C#)で構築できる最新のオブジェクトデータベースマッパーです。
LINQクエリ、変更追跡、更新、スキーマ移行をサポートします。(原文日本語翻訳)
引用:https://www.nuget.org/packages/Microsoft.EntityFrameworkCore
- 以下でEntityFrameworkCore.Designをインストール
dotnet add package Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.Designとは
Entity Framework Coreツールは、設計時の開発作業を支援します。
主に、マイグレーションを管理したり、データベース.DbContext
のスキーマをリバースエンジニアリングすることで、エンティティタイプを足場するために使用されます。
このパッケージは、コマンドラインまたはパッケージマネージャーコンソールベースのツーリングに必要で、dotnet-efとMicrosoft.EntityFrameworkCore.Tools.Microsoft.EntityFrameworkCore.Designの依存関係です。(原文日本語翻訳)
引用:https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Design/
- Microsoft.Extensions.Configurationをインストール
dotnet add package Microsoft.Extensions.Configuration
Microsoft.Extensions.Configurationとは
Microsoft.Extensions.Configurationは、コアコンフィギュレーション抽象化と組み合わされており、.Configurationの形式でキー/値ペアのコンフィギュレーション値を取得するために、さまざまな種類のコンフィギュレーションプロバイダを構築することができます。
環境変数、インメモリコレクション、JSON、INI、またはXMLファイルから読み取るための組み込みコンフィギュレーションプロバイダの実装が多数あります。
組み込みのバリエーションとは別に、さまざまな設定サービスやその他のデータソースと統合するために、コミュニティから出荷されたライブラリもあります。Microsoft.Extensions.Configuration.AbstractionsIConfiguration(原文日本語翻訳)
引用:https://www.nuget.org/packages/Microsoft.Extensions.Configuration/
- Swashbuckle.AspNetCore -v 6.6.2をインストール
dotnet add package Swashbuckle.AspNetCore -v 6.6.2
Swashbuckle.AspNetCoreとは
ASP.NET Coreで構築されたAPIを文書化するためのSwaggerツール
- 以下でSqlServerをダウンロード
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.SqlServerとは
Microsoft.EntityFrameworkCore.SqlServerは、Microsoft SQL ServerおよびAzure SQL用のEF Coreデータベースプロバイダパッケージです。
引用:https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer/
`dotnet add package`コマンド
dotnet add package - プロジェクト ファイルにパッケージ参照を追加または更新します。
既存のファイルの書き換え
- Program.csを書き換える
builder.Services.AddSwaggerGen();
の下に書く
var configuration = builder.Configuration; // Use the Configuration property of the builder object
builder.Services.AddDbContext<BookContext>(options => options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddEndpointsApiExplorer();
を削除し、以下を追記する
builder.Services.AddControllers();
app.UseHttpsRedirection();
の下に追記する
app.MapControllers();
一番上に、以下を追記する
using Microsoft.EntityFrameworkCore;
using SimpleWebApi.Data;
var app = builder.Build();
の下に追記
// Apply pending migrations
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<BookContext>();
context.Database.Migrate();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while migrating the database.");
}
}
app.MapControllers();
の上に追記
app.UseRouting();
追記したコードの説明
-
builder.Configuration;
configurationを利用するために指定しています。 -
builder.Services.AddDbContext<BookContext>(options => options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
-
AddDbContext
とは?指定されたコンテキストを IServiceCollection にサービスとして登録します。(原文日本語翻訳)
-
UseSqlServer
とは?Microsoft SQL Server データベースに接続するようにコンテキストを設定しますが、 DbConnection や接続文字列の初期設定は行いません。(原文日本語訳)
-
GetConnectionString
とは?GetConnectionString() メソッドは、キーが
ConnectionStrings:<connection string name>
である構成値を探していることに注意してください。
-
引用:https://learn.microsoft.com/ja-jp/ef/core/miscellaneous/connection-strings
引用:https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.sqlserverdbcontextoptionsextensions.usesqlserver?view=efcore-8.0
引用:https://learn.microsoft.com/ja-jp/dotnet/api/microsoft.extensions.dependencyinjection.entityframeworkservicecollectionextensions.adddbcontext?view=efcore-8.0
builder.Services.AddControllers();
指定した IServiceCollection にコントローラ用のサービスを追加します。
このメソッドは、ビューやページで使用するサービスは登録しません。(言語日本語翻訳)
- 以下のusing
using Microsoft.EntityFrameworkCore;
using SimpleWebApi.Data;
using ディレクティブを使用すると、その型の完全修飾名前空間を指定せずに、名前空間で定義された型を使用できます。
引用:https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/using-directive
app.MapControllers();
REST API では、属性ルーティングを使用して、HTTP 動詞で操作を表現するリソースのセットとしてアプリの機能をモデル化する必要があります。
属性ルーティングでは、属性のセットを使ってアクションをルート テンプレートに直接マップします。 次のコードは、REST API では一般的であり、次のサンプルで使用されます。
引用:https://learn.microsoft.com/ja-jp/aspnet/core/mvc/controllers/routing?view=aspnetcore-8.0
- 以下のコードの説明
// Apply pending migrations
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<BookContext>();
context.Database.Migrate();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while migrating the database.");
}
}
このコードは、ASP.NET Coreの依存性注入(DI)システムを使用して、データベースのマイグレーションを行うためのものです。
app.Services.CreateScope()
は、新しいスコープを作成します。スコープは、依存性注入コンテナからサービスを解決するための一時的な境界を提供します。var services = scope.ServiceProvider;
で、スコープからサービスプロバイダを取得します。サービスプロバイダは、登録されたサービスを解決するためのメカニズムを提供します。var context = services.GetRequiredService<BookContext>();
で、BookContext
という名前のサービスを解決します。これは、Entity Framework CoreのDbContext
の一部で、データベースとの対話を管理します。context.Database.Migrate();
は、データベースのマイグレーションを実行します。これにより、データベーススキーマが最新の状態に更新されます。catch (Exception ex)
ブロックでは、マイグレーション中にエラーが発生した場合に、そのエラーをログに記録します。
(Copilotで生成)
app.UseRouting();
UseRouting では、ルートの照合がミドルウェア パイプラインに追加されます。
このミドルウェアによって、アプリで定義されているエンドポイントのセットが調べられ、要求に基づいて最適な一致が選択されます。
引用:https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/routing?view=aspnetcore-8.0
以下は削除
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
Program.csの全コード
using Microsoft.EntityFrameworkCore;
using SimpleWebApi.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddControllers();
builder.Services.AddSwaggerGen();
var configuration = builder.Configuration; // Use the Configuration property of the builder object
builder.Services.AddDbContext<BookContext>(options => options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
// Apply pending migrations
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<BookContext>();
context.Database.Migrate();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while migrating the database.");
}
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseRouting(); // 追加
app.MapControllers(); //追加
app.Run();
- appsettings.jsonを書き換える
<PASSWORD>には、適切なパスワードを、<DATABASE_NAME>には、任意のデータベース名を入れてください。
"ConnectionStrings": {
"DefaultConnection": "Server=sql;Database=<DATABASE_NAME>;User Id=sa;Password=<PASSWORD>;MultipleActiveResultSets=true;TrustServerCertificate=True;"
},
ConnectionStringsとは?
接続文字列を指定します。
-
Server=xxx
Serverの名前を指定します。 -
Database=<DATABASE_NAME>
接続するデータベースの名前を指定します。 -
User Id=sa
データベースへの接続に使用するユーザーIDを指定します。 -
Password=<PASSWORD>
ユーザーIDに関連付けられたパスワードを指定します。
MultipleActiveResultSets=true
複数のアクティブな結果セットの有効化をする
TrustServerCertificate=True
TrustServerCertificate キーワードは、有効な証明書を使用して SQL Server インスタンスに接続する場合にのみ使用できます。
TrustServerCertificate を true に設定した場合、トランスポート層に SSL が使用されてチャネルが暗号化されます。また、証明書チェーンをたどることによる信頼性の検証は省略されます。
引用:https://learn.microsoft.com/ja-jp/dotnet/framework/data/adonet/connection-string-syntax
-
DefaultConnection
GetConnectionString()
で指定する名前です。
今回はDefaultConnection
としています。
Dockerでデータベースを作り、接続する
- Dockerfileを作成する
XXXXはポートを入力してください。
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE XXXX
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["dotnet-api-sample.csproj", "."]
RUN dotnet restore "./dotnet-api-sample.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "dotnet-api-sample.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "dotnet-api-sample.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY /app/publish .
ENV ASPNETCORE_URLS="http://*:XXXX"
ENTRYPOINT ["dotnet", "dotnet-api-sample.dll"]
Dockerfileの記述内容の読み方
仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のp180ページより引用↓
- FROM:元にするイメージを指定する
- WORKDIR:RUN、CMD、ENTRYPOINT、ADD、COPYの際の作業ディレクトリを指定する
- COPY:イメージにファイルやフォルダを追加する
- RUN:イメージをビルドするときにコマンドを実行する
- ENV:環境変数を定義する
- ENTRYPOINT:イメージを実行するときのコマンドを強要する
以下は、マルチステージ ビルドを使う - Docker ドキュメントより引用↓
- AS:ステージに対して名前を付ける
- 以下のコマンドをうち、Migrationファイルを作成する
dotnet ef migrations add Initial
dotnet ef migrations add Initialとは
EF Core により、ご自分のプロジェクトに Migrations というディレクトリが作成され、いくつかのファイルが生成されます。
- 以下のコマンドをうち、イメージを作成する
<IMAGE_NAME>には、任意のイメージ名を入れてください。
docker build -t <IMAGE_NAME> .
- docker-compose.ymlを作る
<IMAGE_NAME>には、先程設定したものを、XXXXはポートを、<PASSWORD>には、適切なパスワードを、<DATABASE_NAME>には、任意のデータベース名を入れてください。
# Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP.NET Core service.
version: '3.4'
services:
simplewebapi:
image: <IMAGE_NAME>
build:
context: .
dockerfile: Dockerfile
depends_on:
- sql
environment:
- ConnectionStrings__DefaultConnection=Server=sql;Database=<DATABASE_NAME>;User Id=sa;Password=<PASSWORD>;MultipleActiveResultSets=true;TrustServerCertificate=True;
ports:
- XXXX:XXXX
sql:
image: "mcr.microsoft.com/mssql/server:2022-latest"
container_name: sql-server-zenn
ports: # not actually needed, because the two services are on the same network.
- "1433:1433"
environment:
- ACCEPT_EULA=y
- SA_PASSWORD=<PASSWORD>
docker-compose.ymlの記述内容の説明
simplewebapi,sql
:コンテナ名
仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のP213より引用
services:コンテナに関する定義をする
image:利用するイメージを指定する
depends_on:別のサービスに依存することを示す
environment:環境変数を定義する
ports:ポートのマッピングを指定する
build:構築時に適用するオプションを指定
container_name:デフォルトで生成される名前の代わりに、カスタム・コンテナ名を指定
※Docker コンテナ名はユニークである必要がある
- 2022_latestバージョンをダウンロードする
docker pull mcr.microsoft.com/mssql/server:2022-latest
docker pullとは
仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のP81より引用
docker pull
: Docker Hubなどのリポジトリからイメージをダウンロードする
- docker-composeを実行する
ディレクトリがdotnet-api-sampleの状態で、以下を実行します。
docker-compose up -d
upオプション
仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のp223ページより引用↓
up
:コンテナを作成し、起動する
- 動作確認する
http://localhost:XXXX/api/books
にアクセスして、正常に動作すればOKです。
データベースに値が入っていないので、以下のように表示されますが、適切です。
もし、値を入れたいという場合は、Azure Data Studioを使って行うのが早いと思います。
参考サイトに掲載されている以下のクエリを実行すれば、値が確認できるようになります。
INSERT INTO Books (Title, Author) VALUES (N'たったひとつの冴えたやりかた',N'ジェイムズ・ティプトリー・ジュニア'); INSERT INTO Books (Title, Author) VALUES (N'アンドロイドは電気羊の夢を見るか?',N'フィリップ・K・ディック'); INSERT INTO Books (Title, Author) VALUES (N'夏への扉',N'ロバート・A. ハインライン'); INSERT INTO Books (Title, Author) VALUES (N'幼年期の終り',N'アーサー C クラーク'); INSERT INTO Books (Title, Author) VALUES (N'われはロボット',N'アイザック・アシモフ');
SQLコマンドについて
INSERT INTO <テーブル名> (カラム1️, カラム2, カラム3, ...) VALUES (バリュー1, バリュー2, バリュー3, ...);
(原文日本語訳)
参考サイト
終わりに
いかがでしたでしょうか?
手順に沿っていくだけで、Docker上のSQL Serverに接続し、それを用いたデータベース、APIが動くことが確認できたでしょうか?
一部記述に誤りがあったり、ここはこういう書き方のほうがいいなどありましたら、気軽にコメント頂ければ幸いです。
Discussion