見出し画像

[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)ロジックを別途実装する。

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

この記事が気に入ったらサポートをしてみませんか?