Entity Framework Core (Entity Framework 7)を試してみる

いつの間にやらEntity Framework Coreがリリースされてたのですね。 Entity Framework Core を試しつつ、Entity Framework 6.xとの違いを見ていこうと思います。

Sqliteが公式になってる

公式サイトのDatabase ProviderにSqliteの文字が!

EntityFramework/README.md at dev · aspnet/EntityFramework · GitHub

EntityFrameworkの中の人が作ってるのかな? SQLServerはLocalDBがあるとは言え、割と重いですし、 ファイルを消すだけでさくっとDBを消せるSqliteは魅力です。 単体テストも書きやすいですしね。 単体テストに関してはInMemoryが追加されたので、気になるところ。

SqliteでMigrationに対応してる!

今まではCode Firstでテーブルを作ることはできましたが、 モデルの変更を検知して既存のテーブルに差分を適用する、 なんてことはできませんでした。

これができるのがSQLServerだけだったイメージです。 それが、公式になっているせいかSQLiteも対応していました。

もしかすると共有の機能が強化され、他のDBでもできるかもしれません。

SqliteでDateTimeOffsetに対応してる

日付を扱ううえでDateTimeOffsetを使うことがあります。 Entity Framework 6.xではSqliteの場合はDateTimeOffset型は非対応でした。 Entity Framework Coreでは、一応使えました。 ただし、Offset(タイムゾーンを表す)は0にすること、が要件のようなので 実質DateTimeとかわりません。 しかし、SQLServerSqliteで同じデータモデルが使えるのが良いかもしれません。 もう少し調べたいところですが。

Select句が柔軟になってる!

Entity Framework 6.xではSelectメソッド、WhereメソッドはSQLに変換されるため、 C#ラムダ式とはいえ割と制限がありました。 例えば、こんな呼び方はエラーになります。

class Program
{
    static void Main(string[] args)
    {
        using (var context = new TempContext())
        {
            var people = context.People.Select(_ => new PersonDomainModel(_.Id, _.Name)); //<-複雑なSelectメソッド
        }
    }
}
public class TempContext : DbContext
{
    public DbSet<Person> People { get; set; }
    //(略)
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class PersonDomainModel
{
    public PersonDomainModel(int id, string name)
    {
        Id = id;
        Name = name;
    }
    public int Id { get; }
    public string Name { get; }
}

この制限がわかりにくくて、ランタイムエラーになるのが気になっていました。

それが、Entity Framework Coreではさらっと通ります。 しかもログを出してみると、ちゃんと必要なColumnだけクエリされてるっぽい。

SELECT TOP(1) [].[Id], [].[Name] FROM [People] AS [_]

Selectメソッドの式木を解析して、使われているColumnだけSQLに変換しているのでしょう。 ただし、Whereメソッドは全レコードをC#側で実行されるようなので Whereメソッドは注意が必要です。

この機能は、Client Evaluation というのかな?

Client vs. Server Evaluation — Entity Framework Core 1.0.0 documentation

設定でOffにできるようですが、WhereメソッドだけOffにできたらいいんだけどできるのかな?

Database.SetInitializerが無い

SQLServerの場合、こんな感じ書くと起動時にデータベースをクリアしてくれました。

Database.SetInitializer(new DropCreateDatabaseAlways<EntitiesContext>());

また、データモデルからテーブルを自動Migrationなんてこともできたはず。 この機能は無くなっていました。

Visual Studioのパッケージマネージャコンソールでコマンドを打ち込むことでデータベースは更新するようです。

Add-Migration
Update-Database

ただ、この機能を使うにはNugetで Microsoft.EntityFrameworkCore.Tools を追加する必要がありますが、 2016/7/23現在、Pre-Releaseのようです。

.NET Core CLI

.NET Core CLI というキーワードがちょいちょい出てきます。

.NET - Powerful Open Source Development

この.NET Core CLIでもデータベースの更新ができるようです。 まだ使ったことはないのですが、上のMigrationを使うのにVisual Studioが必要なので Visual Studio無しでデータベースを更新する仕組みなんですかね?

非サイレントインストーラでPowerShell DSCする(Windows7)

Infrastructure As Code

流行っているのか流行っていないのか、Infrastructure As Codeという概念があります。 環境の定義をコードで書いておけば、いつでもその状態を作ることができる、開発者としては理想的な状態です。

Chefが有名ですが、WindowsならPowerShell DSCですね。

しかし、過去のしがらみ等でどうしてもサイレントインストールできないインストーラでセットアップしないといけない場合があります。 (弊社のくそソフトとか)

そんな悪条件のなか、PowerShell DSCを頑張ってみようというのが今回の趣旨です。

条件

インストーラーの自動操作スクリプトを組む

UIの自動操作系のツールはいろいろあります。それらでマウスやキーボードを自動操作してインストール作業を自動化します。

自動操作系ツールの有名どころはこの辺でしょうか。

特筆するところは無いのですが、インストーラーなので管理者権限で動かすこと、Powershellから自動操作をスタートできること、が必要です。

ちなみに私は日本語情報が多かったのでUWSCでいってみました。

リモートから自動操作する

さて、自動操作ができるようになれば、それをリモートから行いましょう。

これができるようになれば、一度に何十台をセットアップするとか、自動デプロイとかができるようになります。

PSExecを使う

SysInternalSuiteに入っているPSExecを使うとリモートから管理者権限でプログラムをスタートできます。

参考

UACを乗り越えて外部PCにソフトウェアをインストールする - 亀岡的プログラマ日記

実はこれで十分だったりするのですが、今回のテーマはPowershell DSC、やはり無駄に「べき等性」とか「あるべき姿に収束する」とか言いたいじゃないですか。

※関係ないですが、このInfrastructure As Codeで出てくる用語って妙に厨二心をくすぐる気がします。

PowerShell DSCから自動操作

PowerShell DSCからインストーラ自動操作のスクリプトを呼び出せば・・・、と思いがちですが、実は非常に面倒だったりします。 理由は、PowerShell DSCからのリモート呼び出しはセッション 0になること。 要するにデスクトップがないため画面表示されません。 そのため、上で作った自動操作スクリプトはそのままでは動かせません。

だったらPSExecでいいじゃん、と思いますが、やはり「あるべき姿に収束する」とか言いたい(略

PowerShell DSCでUIを操作する

仕方がないので、タスクスケジューラを使います。 以下のコマンドで、タスクの作成と実行が行えます。管理者権限(/RL HIGHEST)を付けるのを忘れずに。 また、「ユーザがログオンしているときのみ実行する」にしないとやはりセッション 0になってしまいます。

schtasks.exe /Create /SC ONCE /TN TaskName /IT /F /RL HIGHEST /ST 00:00 /SD 2000/01/01 /RU "(ユーザ名)" /RP "(パスワード)" /TR "(実行パス)"
schtasks.exe /Run /TN TaskName

実行が終わったら後始末も忘れずに。

schtasks.exe  /Delete /F /TN TaskName

PowerShellのコマンドレットにタスクスケジューラを操作するものがあるのですが、 Windows7の場合は機能が足りませんでした。そのため、schtasks.exeを使います。

Windowsへの自動ログイン

タスクスケジューラを設定しても、ログインしていないと動作しません。 先にログインしておけばと思いますが、セットアップの過程で再起動することもあるので、ログイン状態は維持できません。

対策として、Windowsの自動ログインを設定します。

自動ログインは以下のレジストリを設定して再起動すれば可能になります。

HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
"AutoAdminLogon" = "1"
"DefaultUserName" = "ユーザ名"
"DefaultPassword" = "パスワード"

ここらで、いろいろ複雑になってきて、嫌になってきました。 しかし、厨二心に火をつけて頑張りましょう。

え?セキュリティ??

あきらめてください。 今は、非サイレントインストーラをリモートで自動インストールするというムチャをやっているのです。

完成系

上の検討結果をPowerShell DSCのコードにしてみます。 自動操作系のスクリプトはすでに配置済み(C:\Tools\Install-Tools.ps1)という想定です。 必要ならば、配置する部分もPowerShell DSCで表現すればよいでしょう。

DSC.ps1

Set-PSDebug -Strict
$ErrorActionPreference = "Stop"
$WarningPreference = "Continue"
$VerbosePreference = "SilentlyContinue"
$DebugPreference = "SilentlyContinue"

$scriptDir = Split-Path (&{$MyInvocation.ScriptName}) -Parent

. (Join-Path $scriptDir "Serialize-Credentails.ps1")


$configuraionData = @{
    AllNodes = 
    @(
        @{
            NodeName = "*"
            PSDscAllowPlainTextPassword = $true
        },
        @{
            NodeName = "192.168.0.2"
            AdminUser = Get-Credential "Admin"
        }
    )
}

Configuration TestSetup 
{
    Node $AllNodes.NodeName
    {
        # 自動ログオン
        Script Autologin
        {
            TestScript = ({
                $Key = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'

                $curAutoAdminLogon = (Get-ItemProperty $Key).AutoAdminLogon
                $curUser = (Get-ItemProperty $Key).DefaultUserName
                $curPassword = (Get-ItemProperty $Key).DefaultPassword

                if($curAutoAdminLogon -ne "1")
                {{
                    return $false
                }}
                if($curUser -ne "{0}")
                {{
                    return $false
                }}
                if($curPassword -ne "{1}")
                {{
                    return $false
                }}
                return $true

            }-f @($Node.AdminUser.UserName,  $Node.AdminUser.GetNetworkCredential().Password))
            GetScript = {
                $Key = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'

                $curAutoAdminLogon = (Get-ItemProperty $Key).AutoAdminLogon
                $curUser = (Get-ItemProperty $Key).DefaultUserName
                $curPassword = (Get-ItemProperty $Key).DefaultPassword

                return @{
                    TestScript = $TestScript
                    SetScript  = $SetScript
                    GetScript  = $GetScript
                    Result     = $curAutoAdminLogon -eq "1"
                }
            }
            SetScript = ({
                $Key = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'

                Set-ItemProperty $Key -name AutoAdminLogon -Value "1"
                Set-ItemProperty $Key -name DefaultUserName -Value "{0}"
                Set-ItemProperty $Key -name DefaultPassword -Value "{1}"

                # Setting the global:DSCMachineStatus = 1 tells DSC that a reboot is required
                $global:DSCMachineStatus = 1

            }-f @($Node.AdminUser.UserName,  $Node.AdminUser.GetNetworkCredential().Password))
        }

        # インストール処理(タスクスケジューラ呼び出し)
        Script Install
        {
            TestScript = {
                Test-Path C:\tools\SetupTarget -PathType Container
            }
            GetScript = {return @{
                TestScript = $TestScript
                SetScript  = $SetScript
                GetScript  = $GetScript
                Result     =  Test-Path C:\tools\SetupTarget -PathType Container
            }}
            SetScript = {
                powershell -f c:\tools\Start-TaskInstall.ps1
            }
        }
    }
}

$scriptDir = Split-Path (&{$MyInvocation.ScriptName}) -Parent
TestSetup -ConfigurationData $configuraionData

Start-TaskInstall.ps1 ・・・ タスクスケジューラを使ってインストールスクリプト( Install-Tools.ps1 )を呼び出す

& Start-Process "schtasks.exe" "/Create /SC ONCE /TN TaskName /IT /F /RL HIGHEST /ST 00:00 /SD 2000/01/01 /RU `"Admin`" /RP `"passw0rd`" /TR `"powershell.exe -f C:\tools\Install-Tools.ps1`""
& schtasks.exe /Run /TN TaskName

do{
    Sleep(1)
    $content = & schtasks.exe /Query /TN TaskName
}while(($content | where { $_ -match "実行中"}).Count -ne 0)

# 実行が終わったら後始末も忘れずに。

& schtasks.exe  /Delete /F /TN TaskName

まあ、一応うごきました・・・が、やはりかなり無理がありようです。

EntityFramework Code First+SQLiteでGuidを扱う

EntityFramework Code FirstでSQLite

EntityFramework Code Firstは、Microsoft謹製のORマッパーとして高機能でなかなか優秀です。 ただ、やはり相性がいいのはSQLServerだなと思っていたら、 いつの間にか、EntityFramework Code FirstでSQLiteが使えるようになっていました。

こののサイトが参考になります。

C# - Entity FrameworkでSQLiteのCodeFirstを利用する - Qiita

問題発生。GUIDが比較できない!?

モデルクラスは省略しますが、Guidで比較するクエリが正しく動作しません。

        var id = Guid.NewGuid();
        using (var context = new UserData())
        {
            context.Users.Add(new User() {Id = id });
            context.SaveChanges();
        }

        using (var context = new UserData())
        {
            var user = context.Users.First(_ => _.Id == id);   // 例外発生 「追加情報:シーケンスに要素が含まれていません」
        }

SQLiteのオプションで回避可能っぽい?

2時間ほど悩んだのですが、この辺の記事が参考になりました。

c# - How does the SQLite Entity Framework 6 provider handle Guids? - Stack Overflow

System.Data.SQLite: View Ticket

どうもSQLiteのEntityFramework用Providerのバグのようで、ConnectionStringに以下ように「BinaryGUID=True」を指定することで動作するようになるようです。

Data Source=c:\mydb.db;BinaryGUID=True;

いろいろ調べて、本質まで理解できていないと思うのですが、 どうもGuidは、EntityFramework+SQLiteでは、uniqueidentifier型に変換されるようで、uniqueidentifier型はBLOB 16バイトのようです。 そして、SQLiteではGuidはバイナリ形式であるX'0123456789ABCDEF0123456789ABCDEF'という形で指定するのですが、 単にGUIDのメモリをバイナリに変換してクエリしてしまったのでしょう。 GUIDは実際にはエンディアンを意識して変換しないといけないはずなので、その辺のバグなんですかね。

IT技術情報の仕入れ方

会社の飲み会とかで、若手からよく質問されます。

どうやって新しい技術とか調べてくるんですか??

正直、Twitterとかで流れてくる情報を眺めたり、調べものついでに周辺技術を見てみたりと 意識的に情報収集しているつもりはないのですが、いくつかピックアップしてみました。

ThoughtWorks Reader: https://www.thoughtworks.com/radar

ThoughtWorksという有名なソフトウェア会社が定期的に発行している技術トレンド。 要するにイケてる技術者はこういう技術を使いこなすらしい。

言語やプラットフォームがバラバラなので、自分にすぐ役立つ技術は多くないですが、 これは何だろう?と調べて知ってるだけで、いざ問題に直面したときの解決のヒントになったりしますね。 有名になった技術は消えていくので、少し前のReaderを見ると良かったりします。 英語を読むのがしんどいなら、ググってみて日本語情報がないなら流行っていないのでパスしてもいいです。

Chocolatey: https://chocolatey.org/packages

Windows版パッケージシステムです。 技術ではなくツールですが、便利ツールが人気順に並んでいるので上から順に眺めていくと 使えるツールに出会えたりします。 そのツールの解決したい問題を理解したり、内部でどうやっているのかを調べたりするといろいろ広がります。

電脳書房: http://www.bookcyber.net/index.html

オンラインの技術書古本屋。 Twitterでフォローしたり、メーリングリストを登録しておくと技術書の古本の新着情報が届きます。 不要になって売られた本とはいえ、当時は誰かが必要になって買った本なので少し前の技術流行が見えたりします。 また、何度も同じキーワードを見ていると気になってくるもので、ググってみたり、Amazonの評価を見たりすると良いですね。 あと、大体はネットに情報があるとはいえ、全く新しいことをするときは書籍から入った方が体系的になっていたり、初心者向きだったりするので私は好きです。

アドベントカレンダー: http://gihyo.jp/news/info/2014/12/0101

毎年12月になると技術テーマ毎に毎日少しずつ技術情報を公開するお祭りがあります。 正直、毎日コツコツ見るのは面倒なので、面白そうなものをメモっておき、冬休みや年明けの暇なときにまとめて読むのがおすすめです。 もちろん、過去の分もググれば出てくるのでそちらも有益。

ぐぐる: http://www.google.co.jp

まあ、最後はググれだったりします。自分が困っていてPC内で解決できそうなことは、おおむね誰かも困っていて解決策を公開していたりします。 適当にキーワードを並べてググってみると新しいものに出会えるかも。

例えば、UML 差分管理したい → PlantUML とか、C# Html 解析したい → Html Agility Pack とか、WORD 自動生成 C# → Open XML SDK とか、だったりします。

と、いろいろ書き出してみましたが、上のTwitterの例のように日常的に新しい情報を目にするような仕組みを入れる方が良いのかも知れまん。 有名な人をTwitterでフォローするとか、技術トレンドに詳しい友達と毎日ランチするとか、通勤時間は技術系雑誌を読むようにするとか。

あと、昨日の飲み会では女性がいるので言わなかったですが、世の男性はなぜかエロに関しては無駄にパワーがでたりしますよね・・・。

自作ツールの公開場所で悩む->GitHubにあったんだ!

最近、GitHubを使ってオープンソースでいろいろ便利ツールを作っています。

OutlookAddinに関しては、Chocolateyパッケージも作りました。

Chocolateyで困ったのですが、自作ツールインストーラを公開する場所って意外と悩むんですよね。

Copy.com とか レンタルサーバー|さくらインターネット - 無料お試し実施中 とかを試してみましたが、 ちょっといまいち・・・と思ってたら、 GitHubってリリースにダウンロード用のファイルを公開できるんですね。

Copy.comやGoogleDriveのような外部ストレージと違って、長いURIは付かず、 リリースタグの名前が決まればURIが決まる! さすがは、GitHubというところです。

ということで、今後もGitHubを応援していきます。

Outlook MessageIdToolsAddin 1.1.2 とChocolatey

予告したようにChocolateyにも公開しておきました。 ただし、承認されるまで少し時間がかかるそうです。

Chocolatey Gallery | Outlook MessageIdTools Addin 1.1.2

なお、アンインストールスクリプトのためにインストーラを少しいじったので バージョンは上がって、1.1.2です。

https://copy.com/vKZJWSNYjjiog4NH/OutlookMessageIDToolsAddin.1.1.2.msi

Chocolatey で間違ったインストーラが実行される??

Chocolateyはみなさんご存知ですよね?

Windowsにおけるapt-get、yumのようなパッケージ管理ツールです。 開発用ツール群なんかはこれを使うと簡単にまとめてインストールできるので便利に使っています。

さてさて、前回の記事(http://banban525.hatenadiary.jp/entry/2015/06/03/213255) でOutlook MessageIdTools AddinのChocolatey用パッケージを作るつもりと書きました。 そのパッケージを作っていたのですが、どうも挙動がおかしい・・・。

1.1.2のパッケージを作ってインストールしても、1.1.1のインストーラが実行されてしまいます。 よく見ると、1.1.2のインストール中はダウンロードが走ってないような・・・。

さらに調べてみると、Chocolateyはインストーラのキャッシュを持っているらしく、 %TEMP%Chocolatey に古いインストーラが残っていました。

要するに、Chocolateyは過去にパッケージをインストールしていて、アンインストール後に違うバージョンのインストールを行っても、 初回のインストーラが実行される・・・ような動きです。

そんなわけあるかーい、なので勘違いであってほしいのですが、 GitHubのIssuesにも一度バグが起票されて、修正しない扱いになってるようですね。

[BUG] Chocolatey install command (not update command) uses an older cached installer even when upgrading · Issue #522 · chocolatey/chocolatey · GitHub

う~ん、私のパッケージのつくり方の問題ですかねぇ。

期待しているツールだけに、バグではないことを祈ります。