Grow up

生活とプログラミング

DockerでSQL Serverのデータベースをリストアして起動する

f:id:knkomko:20210430223349p:plain

はじめに

これまでSQL Serverを利用した開発は、EC2インスタンスを使用していました。
Dockerを使うと手元の環境で SQL Server を起動して開発を行う事が出来るので
今回はDockerの利点である環境の自動構築を行い、接続までの手順をまとめます。

開発環境

・Widnows10 Pro
・Docker Engine Community Version 20.10.5

全体像

docker compose コマンドで環境の自動構築を行う時にSQL ServerのバックアップファイルをリストアすることでEC2利用時と同じデータベースを再現します。

f:id:knkomko:20210430223815p:plain

使用するファイル
C:\DOCKER\SAMPLE-MSSQL
│  docker-compose.yml
│
├─ mssql
│      db-init.sh
│      ddl.sql
│      Dockerfile
│      entrypoint.sh
│
├─ mssql-backup
│      sampledb.bak
│
└─ mssql-data
docker-compose.yml
version: '3'

services:
   db:
     build: 
      context: ./mssql/
      dockerfile: ./Dockerfile
     container_name: sample-mssql-2017
     ports:
      - "1433:1433"
     environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=Sa@mple@dm1n
      - MSSQL_PID=Express
      - MSSQL_LCID=1041
      - MSSQL_COLLATION=Japanese_CI_AS
      - PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
     volumes:
      - ./mssql-data:/var/opt/mssql
      - ./mssql-backup:/var/opt/mssql/data/backup
Dockerfile
FROM mcr.microsoft.com/mssql/server:2017-latest

COPY ./entrypoint.sh /usr/src/entrypoint.sh
COPY ./db-init.sh /usr/src/db-init.sh
COPY ./ddl.sql /usr/src/ddl.sql

WORKDIR /usr/src/

RUN chmod +x /usr/src/db-init.sh

CMD /bin/bash ./entrypoint.sh
entrypoint.sh

デバッグし易いように db-init.sh の実行ログを log.txt として残しています。

 ./db-init.sh >> /var/opt/mssql/data/backup/log.txt 2>&1 & /opt/mssql/bin/sqlservr
db-init.sh

25s の待ち時間はリストアを行う為にSQL Serverが起動するまで待ちます。
10s の待ち時間はデータベースユーザを追加する為にリストアの終了を待ちます。

/bin/sleep 25s

touch /var/opt/mssql/data/sampledb.mdf
touch /var/opt/mssql/data/sampledb_Log.ldf

/opt/mssql-tools/bin/sqlcmd -H localhost -U sa -P S@mple@dm1n -Q "RESTORE DATABASE sampledb FROM DISK = '/var/opt/mssql/data/backup/sampledb.bak' WITH  FILE = 1, STATS = 5, REPLACE, MOVE 'sampledb' TO '/var/opt/mssql/data/sampledb.mdf', MOVE 'sampledb_Log' TO '/var/opt/mssql/data/sampledb_Log.ldf'"

/bin/sleep 10s

/opt/mssql-tools/bin/sqlcmd -H localhost -U sa -P S@mple@dm1n -i /usr/src/ddl.sql
ddl.sql
EXECUTE AS LOGIN = 'sa'

USE sampletable
GO

DROP USER SampleUser

CREATE USER SampleUser  for login SampleUser

ALTER USER SampleUser 
    WITH DEFAULT_SCHEMA = dbo
GO

GRANT CONNECT TO SampleUser
GO

exec sp_addrolemember 'db_owner', SampleUser
GO
SQL Server の起動
docker compose up --build
SQL Server の接続

SQL Server Management Studio を使用して接続ができることを確認しました。
f:id:knkomko:20210430221003p:plain

C#から踏み台サーバを経由してSQL Serverに接続する

はじめに

複数人による開発でSQL Serverに接続できないかと考えました。
単純にEC2のポートを開けると不正にログインを試みるbotに耐えられずSQL Server が停止しまうため、踏み台サーバを経由した接続を行うことにしました。

全体像

A : 踏み台サーバ
B : データベースサーバ
f:id:knkomko:20210325072334p:plain

A : 踏み台サーバ

SSH接続をする22番ポートを開けています。
またSSH接続用アカウントを新規作成しています。
f:id:knkomko:20210326042123p:plain

今回はSSH.NETを使用してポートフォワーディングを行うため、踏み台サーバではポートフォワーディングの設定は行っていません。
f:id:knkomko:20210324014425p:plain

B : データベースサーバ

SQL Serverの規定インスタンスに接続する1433番ポートを開けています。

ソースコード

C#からSSH接続が行えるSSH.NETを使用しました。
github.com

ソースコードはこちらです。
github.com

SSH.NETでは秘密鍵も利用可能です。

// 踏み台サーバのホスト名
string bastionServer = "XXXXXXXXXX";
// 踏み台サーバのアカウント名
string userName = "XXXXXXXXXX";
// 踏み台サーバのパスワード
string password = "XXXXXXXXXX";

ConnectionInfo info = new ConnectionInfo(bastionServer, 22, userName,
    new AuthenticationMethod[] {
         new PasswordAuthenticationMethod(userName, password)
    }
);

ForwardedPortLocal からポートフォワーディングを行っています。

// SQL Server 接続文字列
string connectionString = @"Data Source=127.0.0.1;Integrated Security=False;User ID=XXXXXXXXXX;Password=XXXXXXXXXX";
// データベースサーバのホスト名
string dbServer = "XXXXXXXXXX";

using (var client = new SshClient(info))
{
    client.Connect();
    var forward = new ForwardedPortLocal("127.0.0.1", 1433, dbServer, 1433);
    client.AddForwardedPort(forward);
    forward.Start();
    using (var connection = new SqlConnection(connectionString))
    {
        using (var command = connection.CreateCommand())
        {
            try
            {
                connection.Open();

                connection.Open();

                command.CommandText = @"SELECT count(*) AS count FROM employee";
結果

踏み台サーバを経由してSQL Server に接続する事ができました。
f:id:knkomko:20210326040108p:plain

スマホにある子供の写真を親のLINEへ定期的に送ってみた

f:id:knkomko:20210321123702p:plain

はじめに

コロナの影響で実家に帰れない為、子供の写真を親のLINEへ送っています。
しかし段々とLINEから送る事を手間に感じてしまい、送り忘れも増えました。

画像共有アプリの利用も考えましたが、アプリのインストール方法から親に説明する必要があるため、新しいアプリの利用は現実的な方法ではありません。

そこで写真を撮るだけで親のLINEに送れないかと考え、取り組むことにしました。
また無料で使えるサービスを組み合わせて実現することを目標にしました。

料金

LINEに1日1枚の画像を送る程度であれば無料の範囲で実現できました。

容量15GBまで無料で使えます。
Google のストレージを追加購入する - パソコン - Google ドライブ ヘルプ

APIも無料で使えます。
Google Drive Apis の利用に関して

  • LINE Messaging API

フリープランを使用していて無料です。
f:id:knkomko:20210321202737p:plain
料金プラン|LINE for Business

サービス間の連携

f:id:knkomko:20210321154153p:plain

①画像を読み込む
子供の写真を撮影するスマホGoogleフォトのアプリをインストールしています。
同期の設定によりスマホ内の写真はGoogleフォトのクラウドストレージに自動でバックアップされるので、Google Apps Scriptでその画像を毎朝9時に読み込みます。

②画像をコピー

Googleアカウントを持たない人にも共有する一般公開のフォルダにGoogleフォトの画像をコピーします。
一般公開とすることでLINEに画像の表示を可能にしています。

③メッセージの送信
Googleフォトにコピーした画像のURLをLINEに送信します。
画像のURLは外部から閲覧できるよう部分的に変更をしています。

④画像の表示
LINEを開くとGoogleドライブに保存していた画像が表示されます。

学び

Googleドライブ
LINEに画像を表示するにはドライブのURLを変更する必要がありました。
ドライブのURLから画像にアクセスできるURLに変更しています。

var downloadURL = buff.getUrl().replace("/file/d/", "/uc?id=").replace("/view?usp=drivesdk", "")

【変更前】
https://drive.google.com/file/d/<ファイルID>/view?usp
【変更後】
https://drive.google.com/uc?id=<ファイルID>

②ダウンロードリンク
画像のURLに "=d" を付加すると画像のダウンロードリンクになります。
LINEで送った画像のダウンロードを可能にする為に必要でした。
f:id:knkomko:20210321193336p:plain

③LINE Messaging API
画像を送るには imageThumbnail と imageFullSize を使用します。
変数downloadURLにGoogleドライブの画像のURLを格納しています。

      var token = ["XXXXXXXXXXXXXXXX"]; //LINEで自動通知をする宛先のトークン。
      var options =
      {
        "method"  : "post",
        "payload" : {"message": "Today's photo.",
                      "imageThumbnail" : downloadURL,
                      "imageFullsize"  : downloadURL,
                    }, 
        "headers" : {"Authorization" : "Bearer "+ token}
      };
    
      UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options); 
結果

僕自身はLINEを使う事が無くなり写真を撮るだけで良くなりました。
なにより我が子の成長を母と兄に見てもらえて嬉しく思っています。

f:id:knkomko:20210321125528p:plain:w350

Run MVP sample app on the Android emulator

f:id:knkomko:20201230045042p:plain

はじめに

2021年1月にAndroidアプリを開発する予定ができました。
何も知らない状態から20日で何が開発できるのか見当もつきませんが
まずはMVPアーキテクチャを学ぶことにしました。

MVPを選択した理由はMVCと似ていて分かりやすかったためです。
eh-career.com

MVPのサンプルをエミュレータで実行しようとしたのですが
一筋縄にはいかなかったため実行にあたり操作した内容をまとめます。

Download MVP sample app

リポジトリからソースコードをダウンロードします。
github.com

SDK

architecture-sample の targetSdkVersion を確認します。
f:id:knkomko:20201230035457p:plain

必要に応じてSDKをインストールします。
f:id:knkomko:20201230035726p:plain

Android emulator

バーチャルデバイスを追加します。
f:id:knkomko:20201230040047p:plain

適当なものを選択します。
f:id:knkomko:20201230040452p:plain

SDKに対応したシステムイメージをダウンロードします。
f:id:knkomko:20201230040520p:plain

ダウンロード後は順に進んで完了です。
f:id:knkomko:20201230040725p:plain

Android emulator HYPERVISOR_ERROR

エミュレータを実行すると HYPERVISOR_ERROR が発生する場合の対策です。
f:id:knkomko:20201230041244p:plain

詳細な内容は以下を参照してください。
qiita.com

Qiitaを参考に私が実際に操作した内容になります。
プログラムと機能の削除 -> Windwosの機能の有効化または無効化
f:id:knkomko:20201230040902p:plain

Hyper-VWindowsハイパーバイザープラットフォーム のチェックを外します。
[OK]ボタンを押した後、変更を適用させるためにWindowsを必ず再起動します。
f:id:knkomko:20201230041051p:plain

Android Studio を起動するとエミュレータの設定画面が表示されました。
メモリサイズは規定値の4GBを選択して次へ進みました。
f:id:knkomko:20201230043822p:plain

インストール後のログにエラーが無いことを確認して完了です。
f:id:knkomko:20201230044129p:plain

Encryption unsuccessful

エミュレータを実行すると Encryption unsuccessful になる場合の対策です。
f:id:knkomko:20201230042459p:plain

tratail の回答が参考になりました。
teratail.com

使用するエミュレータで Wipe Data を実行します。
f:id:knkomko:20201230042914p:plain

Run MVP sample app

MVPのサンプルを実行することが出来ました。
f:id:knkomko:20201230044337p:plain:w300

EC2 Ubuntu に Windows Subsystem for Linux (WSL1) からSSH接続する

f:id:knkomko:20201221012630p:plain

はじめに

年末年始の休みを活用して改めてサーバ構築の勉強をしたいと思い
UbuntuWindows から SSH 接続できるようにしたので書き残します。

Ubuntu は EC2 に作成をしました。

SSH接続

調べてみると Windows から SSH 接続は簡単に出来るようです。

・Traterm
eng-entrance.com

SSHクライアント
dev.classmethod.jp

Windows Subsystem for Linux
www.atmarkit.co.jp

今回は WindowsUbuntu が使える機能 Windows Subsystem for Linux (WSL1) をインストールして SSH 接続に使用することにしました。
WSLがあれば毎回 SSH 接続をしなくてもローカルでコマンドの確認が行えます。

Ubuntu EC2 インスタンスの作成

キーペアの作成までこちらのサイトが参考になりました。
www.acrovision.jp

Windows Subsystem for Linux インストール

インストール手順についてはこちらの記事が分かりやすかったです。
qiita.com

Ubuntu EC2 インスタンス SSH 接続

WSL から ssh コマンドを使用する事で接続が可能でした。

ssh -i {秘密鍵}.pem ubuntu@{インスタンスのIPv4パブリックIP}

pemはEC2インスタンス作成時にダウンロードしたキーペアを指定します。

原因は分からなかったのですが接続の確立が出来ないというメッセージ後に
再試行を許可することで SSH 接続が成功しました。

f:id:knkomko:20201221010907p:plain

Setup DB First EntityFrameworkCore SQL Server

f:id:knkomko:20201220181736p:plain

はじめに

.Net Core で EntityFramework を使う際に .NET Standard と違って試行錯誤したのでDBファーストで使用した手順をまとめます。

対象のフレームワーク

今回は.NET Core 3.1を使用しました。
f:id:knkomko:20201220173109p:plain

開発者用 PowerShell

コマンドは全て開発者用PowerShellから実行します。

カレントディレクトリはプロジェクトルートです。

PS C:\develop\WebApplication1\WebApplication1> dir


    ディレクトリ: C:\develop\WebApplication1\WebApplication1


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2020/12/20     17:30                bin
d-----       2020/12/20     17:30                obj
d-----       2020/12/20     17:29                Properties
-a----       2020/12/20     17:29            162 appsettings.Development.json
-a----       2020/12/20     17:29            192 appsettings.json
-a----       2020/12/20     17:30            723 Program.cs
-a----       2020/12/20     17:30           1295 Startup.cs
-a----       2020/12/20     17:29            148 WebApplication1.csproj
dotnet ef ツールのインストール

.NET Core 3.0 以降は .NET SDKdotnet ef ツールが含まれていないため
初めて使用する場合はインストールが必要です。

dotnet tool update --global dotnet-ef
Microsoft.EntityFrameworkCore.Design の追加

スキャフォールディングに必要な Design パッケージを追加します。

dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer の追加

スキャフォールディングに必要なデータベースプロバイダーを追加します。

dotnet add package Microsoft.EntityFrameworkCore.SqlServer
Models フォルダの作成

コードを保存する Models フォルダを作成します。
f:id:knkomko:20201220180522p:plain

スキャフォールディング

データベースから DBContext と Entity のコードを Models フォルダに作成します。

dotnet ef dbcontext scaffold "Server=[ホスト名];Database=[データベース名];persist security info=True;user id=[ユーザーID];password=[パスワード];MultipleActiveResultSets=True" Microsoft.EntityFrameworkCore.SqlServer -o Models
確認

EntityFrameworkCoreを使用してデータベースからコードが作成できました。

f:id:knkomko:20201220180806p:plain

参考資料

docs.microsoft.com

Setup DB First EntityFramework SQLite

f:id:knkomko:20201114012601p:plain


ソリューションファイルの作成

サンプルとして Windows Form アプリケーションを作成しました。
f:id:knkomko:20201113222821p:plain:w300

1. SQLite Compact Toolbox のインストール

拡張機能の管理から SQLite/SQL Server Compact Toolbox をインストールします。
f:id:knkomko:20201113223609p:plain

2. GAC に SQLite をインストール

sqlite-netFx46-setup-bundle-x86-2015-1.0.113.0.exe をダウンロードします。
デザイナコンポーネントは64bit版には含まれていないため32bit版を使用します。
f:id:knkomko:20201113230709p:plain

GACにインストール、デザイナコンポーネントのインストールを選択します。
f:id:knkomko:20201113230318p:plain

ちなみに64bit版のSetupだとデザイナーコンポーネントの選択がありません。
f:id:knkomko:20201113231439p:plain

GACに登録されている事をToolboxから確認します。
f:id:knkomko:20201113232257p:plain

3. System.Data.SQLite NuGet パッケージのインストール

C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.configの
DbProviderFactories タグからバージョン番号を確認します。
f:id:knkomko:20201113234335p:plain

上記で確認したバージョン番号と同じ NuGet パッケージをインストールします。
NuGet Gallery | System.Data.SQLite 1.0.113.6
f:id:knkomko:20201113235317p:plain

インストール後の packages.config

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="EntityFramework" version="6.3.0" targetFramework="net48" />
  <package id="Stub.System.Data.SQLite.Core.NetFramework" version="1.0.113.3" targetFramework="net48" />
  <package id="System.Data.SQLite" version="1.0.113.0" targetFramework="net48" />
  <package id="System.Data.SQLite.Core" version="1.0.113.6" targetFramework="net48" />
  <package id="System.Data.SQLite.EF6" version="1.0.113.0" targetFramework="net48" />
  <package id="System.Data.SQLite.Linq" version="1.0.113.0" targetFramework="net48" />
</packages>

インストール後の App.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
  </startup>
  <entityFramework>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
      <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
    </providers>
  </entityFramework>
  <system.data>
    <DbProviderFactories>
      <remove invariant="System.Data.SQLite.EF6" />
      <add name="SQLite Data Provider (Entity Framework 6)" invariant="System.Data.SQLite.EF6" description=".NET Framework Data Provider for SQLite (Entity Framework 6)" type="System.Data.SQLite.EF6.SQLiteProviderFactory, System.Data.SQLite.EF6" />
    <remove invariant="System.Data.SQLite" /><add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" /></DbProviderFactories>
  </system.data>
</configuration>

今回のApp.configには設定が不足している為 providers タグに設定を追加します。
f:id:knkomko:20201114002938p:plain

変更後の App.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
  </startup>
  <entityFramework>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
      <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
      <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
    </providers>
  </entityFramework>
  <system.data>
    <DbProviderFactories>
      <remove invariant="System.Data.SQLite.EF6" />
      <add name="SQLite Data Provider (Entity Framework 6)" invariant="System.Data.SQLite.EF6" description=".NET Framework Data Provider for SQLite (Entity Framework 6)" type="System.Data.SQLite.EF6.SQLiteProviderFactory, System.Data.SQLite.EF6" />
    <remove invariant="System.Data.SQLite" /><add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" /></DbProviderFactories>
  </system.data>
<connectionStrings><add name="EmployeeDBContext" connectionString="data source=C:\Users\q612.TOHNICHI\source\repos\SQLiteEFSample\Employee.db" providerName="System.Data.SQLite.EF6" /></connectionStrings></configuration>
4. DBファーストの使用

DBファースト用に department テーブルを作成します。
f:id:knkomko:20201114000757p:plain

DBの作成には DB Browser for SQLite を使用しました。
Downloads - DB Browser for SQLite

ADO.NET Entity Data Model を作成します。
f:id:knkomko:20201114001047p:plain

データベースから Code First を選択します。
f:id:knkomko:20201114001148p:plain

SQLite Provider (Simple for EF6 by ErikEJ) を使用します。
f:id:knkomko:20201114001615p:plain

Data Source にDBのファイルパスを指定します。
f:id:knkomko:20201114001730p:plain

テーブルを指定します。
f:id:knkomko:20201114001824p:plain

Department と DBContext が作成されました。
f:id:knkomko:20201114005254p:plain

全件検索した結果を一覧表示します。

using System;
using System.Linq;
using System.Windows.Forms;

namespace SQLiteEFSample
{
    using Model;

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            using (EmployeeDBContext context = new EmployeeDBContext())
            {
                DataGridView.DataSource = context.Department.AsNoTracking().ToList();
            }
        }
    }
}

無事 department テーブルの一覧を表示する事ができました。
[f:id:knkomko:20201114005346p:plain:300]

補足 App.config の修正を行わなかった場合の動作

不足していた System.Data.SQLite を追加せず実行した場合の動作です。

      <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />

SQLの発行を行う際に InvalidOperationException が発生します。

System.InvalidOperationException: 'No Entity Framework provider found for the ADO.NET provider with invariant name 'System.Data.SQLite'. Make sure the provider is registered in the 'entityFramework' section of the application config file. See http://go.microsoft.com/fwlink/?LinkId=260882 for more information.'

f:id:knkomko:20201114002230p:plain