[Entity Framework] Code First で SQL Server の FILESTREAM（バイナリファイル連携）アクセス

SQL Server の [FILESTREAM](https://docs.microsoft.com/ja-jp/sql/relational-databases/blob/filestream-sql-server) はバイナリデータを管理するのに便利な仕組みですが、Entity Framework の Code First ではまだサポートされていません（2019年2月現在）。

たとえば Entity Framework Core では、次の手順で FILESTREAM データにアクセスできるようになります。

#### 1. インスタンスレベルで FILESTREAM を有効化します。

[FILESTREAM の有効化と構成 | Microsoft Docs](https://docs.microsoft.com/ja-jp/sql/relational-databases/blob/enable-and-configure-filestream)

#### 2. FILESTREAM を有効化したデータベースを作成します。

[FILESTREAM が有効なデータベースを作成する方法 | Microsoft Docs](https://docs.microsoft.com/ja-jp/sql/relational-databases/blob/create-a-filestream-enabled-database)

#### 3. モデルクラスに byte 配列型でプロパティを定義します。

```c#
public class FooTable
{
    // ：
    public byte[] FileDataColumn { get; set; }
    // ：
}
```

#### 4. 初期マイグレーションを作成します。

##### [パッケージマネージャーコンソールの場合]

```powershell
Add-Migration <migration name> -Context SampleContext
```

##### [CLI コマンドの場合]

```powershell
dotnet ef migrations add <migration name> --context SampleContext
```

#### 5. Migrations フォルダ内にヘルパークラスを作成します。

`UpTableAfter` メソッドでは、`CREATE TABLE` の後処理として、FILESTREAM に必要な Id 列と制約を定義し、バイナリ列を再作成します。
`DownTableBefore` メソッドでは、`DROP TABLE` の前処理として、バイナリ列と制約を削除します。

```c#
public static class FileStreamMigration
{
    public static void UpTableAfter(MigrationBuilder migrationBuilder, string tableName, string fileDataColumnName)
    {
        migrationBuilder.Sql($"ALTER TABLE [{tableName}] ADD [FsId] uniqueidentifier rowguidcol NOT NULL");
        migrationBuilder.Sql($"ALTER TABLE [{tableName}] ADD CONSTRAINT [UQ_{tableName}_FsId] UNIQUE NONCLUSTERED ([FsId])");
        migrationBuilder.Sql($"ALTER TABLE [{tableName}] ADD CONSTRAINT [DF_{tableName}_FsId] DEFAULT (NewID()) FOR [FsId]");
        migrationBuilder.Sql($"ALTER TABLE [{tableName}] DROP COLUMN [{fileDataColumnName}]");
        migrationBuilder.Sql($"ALTER TABLE [{tableName}] ADD [{fileDataColumnName}] varbinary(max) FILESTREAM NULL");
    }
 
    public static void DownTableBefore(MigrationBuilder migrationBuilder, string tableName, string fileDataColumnName)
    {
        migrationBuilder.Sql($"ALTER TABLE [{tableName}] DROP COLUMN [{fileDataColumnName}]");
        migrationBuilder.Sql($"ALTER TABLE [{tableName}] DROP CONSTRAINT [DF_{tableName}_FsId]");
        migrationBuilder.Sql($"ALTER TABLE [{tableName}] DROP CONSTRAINT [UQ_{tableName}_FsId]");
        migrationBuilder.Sql($"ALTER TABLE [{tableName}] DROP COLUMN [FsId]");
    }
}
```

#### 6. 初期マイグレーションクラスの Up メソッド末尾にヘルパーメソッド呼び出しを追記します。

```c#
public partial class Initial : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
         :
         migrationBuilder.CreateTable(
                name: "FooTable", ...
         :
        FileStreamMigration.UpTableAfter(migrationBuilder, nameof(FooTable), nameof(FooTable.FileDataColumn));
    }
```

#### 7. 初期マイグレーションクラスの Down メソッド先頭にヘルパーメソッドを呼び出しを追記します。

```c#
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        FileStreamMigration.DownTableBefore(migrationBuilder, nameof(FooTable), nameof(FooTable.FileDataColumn));
         :
        migrationBuilder.DropTable(
                name: "FooTable");
         :
```

これで byte 配列を介した [Transact-SQL による FILESTREAM アクセス](https://docs.microsoft.com/ja-jp/sql/relational-databases/blob/access-filestream-data-with-transact-sql) が実現できます。

#### Ignore 方式（検討を推奨）

ファイルサイズに対してよりスケーラブルな [「ファイル システム ストリーミング」（Win32 API）方式](https://docs.microsoft.com/ja-jp/sql/relational-databases/blob/create-client-applications-for-filestream-data) でアクセスする場合や、モデルとは別個にバイナリデータを取得したい場合は、以下のようにします。

- `DbContext` クラスの `OnModelCreating` メソッドでファイルデータ列を [`Ignore`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.metadata.builders.entitytypebuilder.ignore) する。
- `FileStreamMigration.UpTableAfter` メソッドから `DROP COLUMN` のステートメントを除去する。
- バイナリデータ列値の取得（`SELECT`）、保存（`UPDATE`）ロジックを別途実装する。