EntityFrameworkCoreを.NET Core コンソールアプリでCodeFirstに使う

# 執筆時点の環境
* Windows 10 を使用
* VisualStudio Community 2017 (略称 VS) インストール済み
* Microsoft SQL Server Data Tools インストール済み (VSのインストーラに付随)
* .NET Core 1.0.4
* EntityFrameworkCore 1.1.2

# CodeFirstとは
ソースコードを先に書き、それを元にEntityFrameworkにDB・テーブルを生成させるようなスタイル。ルールに則ってコード記述を行うことでDBやテーブルの生成はフレームワーク側が行ってくれる。

基本ルール

* エンティティクラス:  
  扱うデータを表現するクラス。DBのレコードと対応する。何も継承する必要は無く、カラムと対応するプロパティを持つ。
* コンテキストクラス:  
  DbContextを継承させる。管理するエンティティクラスをDbSet\<T\>型のプロパティとして保持させる。レコード取得やレコード保存はこのクラスのインスタンスを通して行う。

# 使ってみる
## VisualStudio でプロジェクト作成
テンプレート「.NET Core」-「Console Application (.NET Core)」でプロジェクト作成
(プロジェクト名はHelloCoreとする)

コンソールに日本語を出力すると文字化けする。
.NET Coreは標準ではUnicode, ASCII, CodePage 28591 以外に対応していない一方、WindowsのコンソールはShift-JISである事が原因。NuGet経由でShift-JISへ対応させるパッケージを導入すれば日本語表示可能。以下の作業で対応させる。

NuGetからパッケージ「System.Text.Encoding.CodePages」をインスール
Mainメソッド先頭に以下の記述を追加  

```diff:Program.cs

        public static void Main(string[] args)
        {
+           //登録すれば必要な場面(Win上で実行した時)で使用されます。
+           System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
```

## EntityFrameworkCoreを使う
SQL Serverと組合せ、指定したフィードから記事タイトルと抜粋を取得しDBへ保存するアプリを作成する。

### NuGetで必要パッケージをインストール。

* Microsoft.EntityFrameworkCore
* Microsoft.EntityFrameworkCore.Design
* Microsoft.EntityFrameworkCore.SqlServer
* Microsoft.EntityFrameworkCore.Tools

### エンティティクラスを作成
データの受け皿となるクラスとしてArticle, AppDbContextを作成。

```csharp:Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace HelloCore
{
    public class Program
    {
        public static void Main(string[] args)
        {
            //登録すれば必要な場面(Win上で実行した時)で使用される。
            System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
        }
    }
    //エンティティクラス
    public class Article
    {
        public int Id { get; set; }
        public string LinkUrl { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public string ChannelTitle { get; set; }
    }
    //コンテキストクラス
    public class AppDbContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            base.OnConfiguring(optionsBuilder);
            //ここでは接続先のDB名はhellocoredbとする
            optionsBuilder.UseSqlServer(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=hellocoredb;");
        }
        public DbSet<Article> Article { get; set; }
    }
}
```

VS上で「パッケージマネージャ コンソール」を開く。
コンソールはMicrosoft.EntityFrameworkCore.Toolsによって追加されたコマンドが使用可能になっています。
これを用いてAppDbContextクラスが宣言したDB・テーブルの生成を行う。

```ps1:PackageManagerConsole
PS> Add-Migration 適当な名前
PS> Update-Database
```
プロジェクトの/Migrations/下に"適当な名前"のMigration派生クラスが生成される。このクラスの内容が実行されることでDB操作が行われる。上のコマンドではAdd-Migrationで派生クラス生成、Update-Databaseで生成クラスを元にDB操作が行われている。

DB生成を間違えた時は、下記操作を行うとやり直し可能。

1. プロジェクトへ追加された"Migrations"フォルダを削除
2. パッケージマネージャコンソールからDrop-Databaseを実行

### Mainメソッドで使ってみる
DBの準備は完了。実際にDBを使用してみる。MainメソッドにDBを使用するコードを記述する。

```csharp:Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace HelloCore
{
    public class Program
    {
        public static void Main(string[] args)
        {
            System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
            Console.WriteLine("開始");

            var db = new AppDbContext();
            var http = new System.Net.Http.HttpClient();

            //フィードを適当なサイトから取得
            foreach (var targetUrl in
                new[] {
                    "http://www3.asahi.com/rss/index.rdf",
                    "http://rss.rssad.jp/rss/codezine/new/20/index.xml",
                })
            {
                //フィードxmlをDL & Parse
                //xmlは名前空間で面倒が生じないよう名前空間情報を除染
                var rssTxt = http.GetStringAsync(targetUrl).Result;
                var rss = System.Xml.Linq.XElement.Parse(rssTxt);
                foreach (var item in rss.Descendants())
                    item.Name = item.Name.LocalName;

                //フィードの記事をModelオブジェクトへ移し替える
                var articles = rss
                    .Descendants("item")
                    .Select(item =>
                        new Article()
                        {
                            Title = item.Element("title").Value,
                            LinkUrl = item.Element("link").Value,
                            Description = item.Element("description").Value,
                            ChannelTitle = rss.Element("channel").Element("title").Value,
                        });

                //DBに未追加の記事をDBへ保存する
                foreach (var item in articles)
                {
                    if (db.Article.Any(_ => _.LinkUrl == item.LinkUrl))
                        continue;

                    Console.WriteLine(item.Title);
                    db.Article.Add(item);
                }
            }
            //DBへの保存を確定
            db.SaveChanges();
            Console.WriteLine("終了");
            Console.Read();
        }
    }
    //エンティティクラス
    public class Article
    {
        public int Id { get; set; }
        public string LinkUrl { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public string ChannelTitle { get; set; }
    }
    //コンテキストクラス
    public class AppDbContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            base.OnConfiguring(optionsBuilder);
            //ここでは接続先のDB名はhellocoredbとする
            optionsBuilder.UseSqlServer(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=hellocoredb;");
        }
        public DbSet<Article> Article { get; set; }
    }
}
```

VisualStudioで実行。
フィードがコンソールに表示され、VSの「SQL Server オブジェクト エクスプローラ」からhellocoredbのArticleテーブルが更新されている事を確認できれば成功。

# その他の操作
## エンティティクラスのメンバを編集したい時
エンティティクラスへメンバを編集後、「パッケージマネージャ コンソール」から以下のコマンドを叩く

```ps1:PackageManagerConsole
PS> Add-Migration 加えた変更が分かる名称
PS> Update-Database
```

## 直前の編集を取り消したい時 (DBアップデート前)
以下のコマンドを叩く。DBアップデート後だった場合には取消しマイグレーションとして上の追加時の操作を行う。

```ps1:PackageManagerConsole
PS> Remove-Migration
```

## DBを消したい時
以下のコマンドを叩く。叩いた後にプロジェクトのMigrationsフォルダを削除

```ps1:PackageManagerConsole
PS> Drop-Database
```

# おまけ
## 使用DBをSQLiteに切替えてみる

### NuGetから必要パッケージをインスール
* Microsoft.EntityFrameworkCore.Sqlite

### AppDbContextを書き換える
これまではMicrosoft.EntityFrameworkCore.SqlServerの外部拡張メソッドによってSQL Serverを使用するよう設定されていました。Sqlite用を使用するよう書き換えてみます。

```diff:Program.cs
public class AppDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        //ここでは接続先のDB名はhellocoredbとする
-       optionsBuilder.UseSqlServer(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=hellocoredb;");
+       optionsBuilder.UseSqlite(@"Data Source='hello.db'");
    }
    public DbSet<Article> Article { get; set; }
}
```

### SQLiteでDBを作成する
```ps1:PackageManagerConsole
PS> Update-Database
```

## CLI版を使ってみる
こっちの方が好み。

```ps1:cmd
# CLI版を使う際には追加パッケージをインストールします。
dotnet add package Microsoft.EntityFrameworkCore.Tools.DotNet

# Add-Migration 適当な名前
dotnet ef migrations add 適当な名前

# Remove-Migration
dotnet ef migrations remove

# Update-Database
dotnet ef database update

# Drop-Database
dotnet ef database drop
```
追加パッケージインストール後には.csprojを開いて入れたパッケージの参照タグの名前を"PackageReference"から"DotNetCliToolReference"に書き換える必要があります。

```diff:HelloCore.csproj
    <PackageReference Include="System.Text.Encoding.CodePages" Version="4.3.0" />
-   <PackageReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.1" />
+   <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.1" />
  </ItemGroup>
</Project>
```

# まとめ

* 規約を守った記述を行っていればDB構築はコマンド一本
* EntityFrameworkCore + SQL Serverで最低限必要なパッケージは無印, Design, SqlServer, Tools
* SQLiteも公式対応。SqlServerの代わりにSqliteパッケージを入れる

# 参考資料
* Entity Framework Core  
  https://docs.efproject.net/en/latest/index.html
* .NET Core での コンソールアプリの文字化けを直す  
  http://aquasoftware.net/blog/?p=895