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


SQL Server の FILESTREAM はバイナリデータを管理するのに便利な仕組みですが、Entity Framework の Code First ではまだサポートされていません(2019年2月現在)。

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

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

FILESTREAM の有効化と構成 | Microsoft Docs

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

FILESTREAM が有効なデータベースを作成する方法 | Microsoft Docs

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

public class FooTable
{
    // :
    public byte[] FileDataColumn { get; set; }
    // :
}

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

[パッケージマネージャーコンソールの場合]
Add-Migration <migration name> -Context SampleContext
[CLI コマンドの場合]
dotnet ef migrations add <migration name> --context SampleContext

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

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

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 メソッド末尾にヘルパーメソッド呼び出しを追記します。

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 メソッド先頭にヘルパーメソッドを呼び出しを追記します。

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

これで byte 配列を介した Transact-SQL による FILESTREAM アクセス が実現できます。

Ignore 方式(検討を推奨)

ファイルサイズに対してよりスケーラブルな 「ファイル システム ストリーミング」(Win32 API)方式 でアクセスする場合や、モデルとは別個にバイナリデータを取得したい場合は、以下のようにします。

  • DbContext クラスの OnModelCreating メソッドでファイルデータ列を Ignore する。
  • FileStreamMigration.UpTableAfter メソッドから DROP COLUMN のステートメントを除去する。
  • バイナリデータ列値の取得(SELECT)、保存(UPDATE)ロジックを別途実装する。

一覧で取得することがある場合は、全体でかなりのデータ量になることもありますので、この方式をお薦めします。