[SQL]FILESTREAM/VARBINARY(MAX)の使い方

[D&DしたPDFファイルをSQL Serverに登録＋プレビューする方法](https://qiita.com/ANNEX_IBS/items/52319d3cbb8678e68eea)の記事の一部になります。

#2. SQL Server:FileStream管理の設定とその実装
リンク先を参考にしていますが、リンク先はSQL Serever 2014で、自分の環境はSQL Serever 2012で、若干違いがあるかもしれません。

##1) BLOB (Binary Large Object ) と FILESTREAM
ファイルを保存するには、二通りの方法があります。

* FILESTREAM を使って、varbinary(max) オブジェクトをファイルシステムに保存する方法
* BLOB を通常の varbinary(max) データとして、テーブルに保存する方法

保存するファイルのサイズが 1 MB より大きく、高速な読み込みが必要な場合は FILESTREAM の利用が推奨されているようです。

##2) FILESTREAMを使ってファイルを保存する方法

###FILESTREAMを有効化する
SQL Server構成マネージャーで、有効化したいデータベースエンジンのインスタンスを右クリックしプロパティを表示します。
![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/6256e18d-79f6-0a4b-0792-b0d00da93d73.png)

FILESTREAMタブで、FILESTREAMを有効化する。
![図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/d25b0401-8771-2a52-3818-2a47f2f5acad.png)

データベースエンジンのインスタンスを再起動します。

###FILESTREAMのアクセスレベルを設定する
SQL Server Managment Studioのクエリーエディタを開き以下のクエリーを実行します。

```SQL
EXEC sp_configure filestream_access_level, 2;
RECONFIGURE;
```

設定可能なアクセスレベル:

0: FILESTREAM サポートを無効
1: Transact-SQL アクセスに対して FILESTREAM を有効
2: Transact-SQL アクセスおよび Win32 ストリーム アクセスに対して FILESTREAM を有効

SQL Server Managment Studioで確認します。
![図4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/eecbaf30-ffa5-e1dc-960e-9e4c680ab261.png)

###FILESTREAMのデータを保存するテーブルを作る
FILESTREAMデータを保存するテーブルを作る例として、以下のスクリプトを実行して、UploadFile という名前のテーブルを作成します。

```SQL
CREATE TABLE UploadFile (
   FileID UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL PRIMARY KEY,
   FileData VARBINARY(MAX) FILESTREAM NULL,
   UploadFileName NVARCHAR(100) NULL
);
```

テーブルに **UNIQUEIDENTIFIER** データタイプで ROWGUIDCOL プロパティを設定した ID となるカラムを作り、FILESTREAMデータを保存するカラムは、データタイプは **VARBINARY(MAX)** にし、FILESTREAM アトリビュートを指定します。

###FILESTREAMを使ってファイルを保存する
C#などで、ファイルをバイト配列に読み込み、そのバイト配列を普通のデータのように Insert します。

```SQL
DECLARE @FileData VARBINARY(MAX) = 0x504B0304140006000800000021...,
        @UploadFileName NVARCHAR(100) = 'Text.xlsx';

INSERT INTO UploadFile (
   FileID,
   FileData,
   UploadFileName
)
VALUES (
   NEWID(),
   @FileData,
   @UploadFileName
);
```

###ファイルをバイト型配列に読み込む、バイト型配列をファイルに書き込む
下記のソースを参考に処理を作成しています。
(通常は、usingステートメントやtry...finallyを使って確実にCloseメソッドが呼び出されるようにします)


```C#
//ファイルを開く
var fs = new System.IO.FileStream(
  @"C:\test.txt",
  System.IO.FileMode.Open,
  System.IO.FileAccess.Read);
//ファイルを読み込むバイト型配列を作成する
var bs = new byte[fs.Length];
//ファイルの内容をすべて読み込む
fs.Read(bs, 0, bs.Length);
//閉じる
fs.Close();
```

###バイト配列⇒16進数文字列へ変換
下記のソースを参考に処理を作成しています。

```C#
/// <summary>
/// バイト配列⇒16進数文字列へ変換
/// </summary>
class ByteArray01
{
  private static byte[] b = { 0x41, 0x42, 0x43 };
  private static string s = string.Empty;

  static void Main(string[] args)
  {
    // s = "414243"
    s = BitConverter.ToString(b).Replace("-", string.Empty);
  }
}
```

##参考
[SQL Server: FILESTREAM（ファイルストリーム） を使ってファイルを保存する方法](https://sql55.com/column/save-files-to-filestream.php)
[FILESTREAM の有効化と構成](https://docs.microsoft.com/ja-jp/sql/relational-databases/blob/enable-and-configure-filestream?view=sql-server-2014&redirectedfrom=MSDN)
[ファイルをバイト型配列に読み込む、バイト型配列をファイルに書き込む](https://dobon.net/vb/dotnet/file/filestream.html)
[【備忘録】byte配列⇒16進数文字列へ変換](https://qiita.com/shusakuorz/items/f90aa57b4ddc2d01e3ea)


##3) BLOB を通常の varbinary(max) データとして、テーブルに保存する方法
BLOB を通常の varbinary(max) データとして、テーブルに保存する方法になります。
FILESTREAMの利用にはWindows 認証が必要なため、**SQL Server 認証 のみの環境**では、こちらとなります。

###サンプルテーブル (myTable)
|列名|データ型|
|:---|:---|
|FileName|tinytext|
|FileData|varbinary(max)|

###OPENROWSET で varbinary(max) を登録する方法
varbinary(max) データとして登録には、OPENROWSETでファイルを指定します。

```SQL
INSERT INTO myTable(FileName, FileData)
  SELECT
    'file.dat' AS FileName
    ,*
  FROM
    OPENROWSET(BULK N'E:\file.dat', SINGLE_BLOB) AS FileData
```

成功すれば、下記のように登録されるはず。
(FileDataの中身は架空)

||FileName|FileData|
|:---|:---|:---|
|1|file.dat|0x255044462D312E3...|

###登録した varbinary(max) をファイル保存する方法
varbinary(max) を取得して、バイト配列としてファイルに保存します。

```C#
/// <summary>
/// varbinary(max)をファイルに保存する
/// </summary>
public void WriteVarbinaryToFile()
{
  // 接続文字列作成
  var builder = new SqlConnectionStringBuilder()
  {
    DataSource = @".\Local_SQLEXPRESS",
    InitialCatalog = "myTable",
    IntegratedSecurity = false,
    UserID = "sa",
    Password = "hogehoge"
  };

  using (var connection = new SqlConnection(builder.ToString()))
  {
    connection.Open();

    var command = new SqlCommand();
    command.Connection = connection;
    command.CommandText = "SELECT FileData FROM myTable WHERE FileName = N'file.dat'";

    // varbinary(max)データ取得
    using (var reader = command.ExecuteReader())
    {
      while (reader.Read())
      {
        // ファイルに保存
        File.WriteAllBytes(@"E:\file.dat", (byte[])reader["FileData"]);
      }
    }
  }
}
```

##参考
[OPENROWSET (Transact-SQL)](https://docs.microsoft.com/ja-jp/sql/t-sql/functions/openrowset-transact-sql?view=sql-server-ver15)
[DB2 10.5にC#から画像データをBLOB列に登録する](https://symfoware.blog.fc2.com/blog-entry-1329.html)