.NET(.NET Core)で mdb ファイルの操作および作成について

# はじめに
これは、[Visual Basic Advent Calendar 2024](https://qiita.com/advent-calendar/2024/vb)の21日目の記事となります。

同僚が、プロジェクトのプロトタイプとして .NET 8(C#) を使用して mdb による操作を行おうとするもコンパイルエラーになるとのことで、調べると .NET(.NET Core)だと NuGetから「System.Data.OleDb」の取得が必要ということが分かりました。取得後に操作は出来るようになったが、配布の問題(別途インストールが必要)などの問題があり、mdb ファイルの使用は中止になったとのこと。

せっかく調べたし、ブログのネタになりそうなので書いてみました。

# mdb ファイルの読込み
.NET 8 を使用して、mdb ファイルからデータの読込みを行います。

## 準備
* NuGetから「System.Data.OleDb 9.0.0」を取得します
https://www.nuget.org/packages/System.Data.OleDb/

* プロジェクトのプロパティのターゲット CPU(プラットフォーム ターゲット)を「x86」に設定します

ターゲット CPU(プラットフォーム ターゲット)を「AnyCPU」とした場合、64bit OSでは、「Microsoft Access データベース エンジン 再頒布可能コンポーネント」などで別途インストールする必要があります。

今回は別途インストールを避ける目的で、32bitのOS標準「OLEDB 4.0」を使用するため、ターゲット CPU（プラットフォーム ターゲット）を「x86」に設定します。

https://qiita.com/yaju/items/86314412741deb806366

## ソースコード
WinForms にボタンとデータビューコントロールを追加して、ボタンクリックイベントでMDBファイル内のテーブルを読込みデータビューに表示します。

### Visual Basic
```vb
Imports System.Data.OleDb

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
{
    Dim basePath As String = AppContext.BaseDirectory
    Dim mdbFileName As String = "test.mdb"
    Dim dt As New DataTable()

    ' データベースをオープン
    Using connection As OleDbConnection = New OleDbConnection()
        connection.ConnectionString = $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={basePath}{mdbFileName}"
        connection.Open()

        Dim adapter As New OleDbDataAdapter()
        Dim command As New OleDbCommand()

        Dim sql As String = "SELECT * FROM 社員"
        command.CommandText = sql
        command.Connection = connection
        adapter.SelectCommand = command

        ' 実行
        adapter.Fill(dt)
    End Using

    DataGridView1.DataSource = dt
 }
```
### C#
```csharp
using System.Data.OleDb;
using System.Data;

private void button1_Click(object sender, EventArgs e)
{
    string basePath = AppContext.BaseDirectory;
    string mdbFileName = "test.mdb";
    DataTable dt = new DataTable();

    // データベースをオープン
    using (OleDbConnection connection = new OleDbConnection())
    {
        connection.ConnectionString = $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={basePath}{mdbFileName}";
        connection.Open();

        OleDbDataAdapter adapter = new OleDbDataAdapter();
        OleDbCommand command = new OleDbCommand();
        string sql = "SELECT * FROM 社員";
        command.CommandText = sql;
        command.Connection = connection;
        adapter.SelectCommand = command;

        // 実行
        adapter.Fill(dt);
    }

    dataGridView1.DataSource = dt;
}
```

# mdb ファイルの作成と読込み
前提として、OLEDB 接続を使用してデータベースを作成することはできません。

その為、Access がインストールされていない環境で mdb ファイルを新規作成するには、
.NET Framework では「Microsoft ADO Ext. 2.8 for DDL and Security」の参照が必要でした。
[Access MDB を.net アプリケーションから新規作成する](https://kakken1988.hatenadiary.org/entries/2011/01/31)

今回はこの参照を行わないで mdb ファイルを作成します。
調べたところ、「EntityFrameworkCore.Jet」を使用すると強制的ですが  mdb ファイルを作成できるようです。
[How to configure the provider to use ADOX? #109](https://github.com/CirrusRedOrg/EntityFrameworkCore.Jet/issues/109)

## 準備
* NuGetから「EntityFrameworkCore.Jet」を追加で取得します
https://www.nuget.org/packages/entityframeworkcore.jet/

## ソースコード
WinForms にボタンをもう一つ追加します。
Access らしさとして、日本語のテーブルと列名を使用してみました。

## Visual Basic
```vb
Imports System.Data.OleDb
Imports EntityFrameworkCore.Jet.Data
Imports Microsoft.EntityFrameworkCore

Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click

    ' データベースをオープン
    Dim basePath = AppContext.BaseDirectory
    Dim mdbFileName = "test2.mdb"

    Dim connectionString As String = $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={basePath}{mdbFileName}"

    ' データベース削除
    JetConnection.ClearAllPools()
    JetConnection.DropDatabase(connectionString)

    ' データベース作成
    JetDatabaseCreator.CreateInstance(SchemaProviderType.Adox).CreateDatabase(connectionString)

    Using context As New MdbTestContext()
        context.ConnectString = connectionString

        ' テーブル作成
        CreateTable(context)

        ' データ登録
        InsertTable(context)

        ' データ表示
        DataGridView1.DataSource = context.社員.ToList()
    End Using

End Sub

' テーブル作成
Private Sub CreateTable(context As DbContext)

    Try
        ' EF Coreでは、この一行でテーブルが作成される
        context.Database.EnsureCreated()

        ' 作成できるのでコメントアウト
        ' Dim cn As JetConnection = context.Database.GetDbConnection()
        ' Dim sql As String = ""
        ' sql += "CREATE TABLE 社員([社員番号] TEXT, [氏名] TEXT, [部門] TEXT"
        ' sql += " ,CONSTRAINT [PrimaryKey] PRIMARY KEY ([社員番号]) )"

        ' cn.Open()
        ' Dim cmd = cn.CreateCommand(sql)
        ' cmd.ExecuteNonQuery()
    Catch ex As Exception
        MessageBox.Show(ex.Message, "エラー")
    End Try
End Sub

' データ登録
Private Sub InsertTable(context As DbContext)

    Try
        context.Add(New 社員() With {.社員番号 = "001", .氏名 = "鈴木 一郎", .部門 = "情報"})
        context.Add(New 社員() With {.社員番号 = "002", .氏名 = "山田 花子", .部門 = "総務"})
        context.SaveChanges()
    Catch ex As Exception
        MessageBox.Show(ex.Message)
    End Try

End Sub
     
' 社員クラス
Public Class 社員
    Public Property 社員番号 As String
    Public Property 氏名 As String
    Public Property 部門 As String
End Class

' MDBコンテキスト
Public Class MdbTestContext
    Inherits DbContext

    Public Overridable Property ConnectString As String

    Public Overridable Property 社員 As DbSet(Of 社員)

    Protected Overrides Sub OnConfiguring(optionsBuilder As DbContextOptionsBuilder)
        MyBase.OnConfiguring(optionsBuilder)
        optionsBuilder.UseJet(ConnectString)
    End Sub

    Protected Overrides Sub OnModelCreating(modelBuilder As ModelBuilder)
        modelBuilder.Entity(Of 社員)().HasKey(Function(am) New With {am.社員番号})
    End Sub
End Class
```

### C#
```csharp
private void button2_Click(object sender, EventArgs e)
{
    // データベースをオープン
    string basePath = AppContext.BaseDirectory;
    string mdbFileName = "test2.mdb";

    string connectionString = $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={basePath}{mdbFileName}";

    // データベース削除
    JetConnection.ClearAllPools();
    JetConnection.DropDatabase(connectionString);

    // データベース作成
    JetDatabaseCreator.CreateInstance(SchemaProviderType.Adox).CreateDatabase(connectionString);

    using (var context = new MdbTestContext())
    {
        context.ConnectString = connectionString;

        // テーブル作成
        CreateTable(context);

        // データ登録
        InsertTable(context);

        // データ表示
        dataGridView1.DataSource = context.社員.ToList();
    }
}

// テーブル作成
private void CreateTable(DbContext context)
{
    try
    {
        // EF Coreでは、この一行でテーブルが作成される
        context.Database.EnsureCreated();

        // 作成できるのでコメントアウト
        /*
        var cn = (JetConnection)context.Database.GetDbConnection();
        string sql = "";
        sql += "CREATE TABLE 社員([社員番号] TEXT, [氏名] TEXT, [部門] TEXT";
        sql += " ,CONSTRAINT [PrimaryKey] PRIMARY KEY ([社員番号]) )";

        cn.Open();
        var cmd = cn.CreateCommand(sql);
        cmd.ExecuteNonQuery();
        */
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "エラー");
    }
}

// データ登録
private void InsertTable(DbContext context)
{
    try
    {
        context.Add(new 社員 { 社員番号 = "001", 氏名 = "鈴木 一郎", 部門 = "情報" });
        context.Add(new 社員 { 社員番号 = "002", 氏名 = "山田 花子", 部門 = "総務" });
        context.SaveChanges();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

// 社員クラス
public class 社員
{
    public string 社員番号 { get; set; } = "";
    public string 氏名 { get; set; } = "";
    public string 部門 { get; set; } = "";
}

// MDBコンテキスト
public class MdbTestContext : DbContext
{
    public virtual string ConnectString { get; set; } = "";

    public virtual DbSet<社員> 社員 { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.UseJet(ConnectString);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<社員>().HasKey(am => new { am.社員番号 });
    }
}
```

## 結果
test2.mdb ファイルの中身
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/e83aa628-1b29-5fe5-221f-0ce19ec7c855.png)

Button2のクリック結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/388aa811-5f39-f702-70f6-86eb71b99674.png)


## ファイル一覧
```
WinFormsMDB_VB\bin\Debug\net8.0-windows
│  EntityFrameworkCore.Jet.Data.dll
│  EntityFrameworkCore.Jet.dll
│  Microsoft.EntityFrameworkCore.Abstractions.dll
│  Microsoft.EntityFrameworkCore.dll
│  Microsoft.EntityFrameworkCore.Relational.dll
│  Microsoft.Extensions.Caching.Abstractions.dll
│  Microsoft.Extensions.Caching.Memory.dll
│  Microsoft.Extensions.Configuration.Abstractions.dll
│  Microsoft.Extensions.DependencyInjection.Abstractions.dll
│  Microsoft.Extensions.DependencyInjection.dll
│  Microsoft.Extensions.Logging.Abstractions.dll
│  Microsoft.Extensions.Logging.dll
│  Microsoft.Extensions.Options.dll
│  Microsoft.Extensions.Primitives.dll
│  System.Configuration.ConfigurationManager.dll
│  System.Data.OleDb.dll
│  System.Diagnostics.EventLog.dll
│  System.Diagnostics.PerformanceCounter.dll
│  System.Security.Cryptography.ProtectedData.dll
│  test.mdb
│  test2.mdb
│  WinFormsMDB_VB.deps.json
│  WinFormsMDB_VB.dll
│  WinFormsMDB_VB.exe
│  WinFormsMDB_VB.pdb
│  WinFormsMDB_VB.runtimeconfig.json
│
└─runtimes
    └─win
        └─lib
            └─net8.0
                    System.Data.OleDb.dll
                    System.Diagnostics.EventLog.dll
                    System.Diagnostics.EventLog.Messages.dll
                    System.Diagnostics.PerformanceCounter.dll
```

# 最後に
今更、mdb ファイルをまだ使うのかと思ったのが第一印象です。せめて mdb じゃなく accdb でしょと accdb に変わってから何年経っているのやら。
これまで長年 32 bit OSが主流だったこともあり、OLEDB 4.0ドライバーが使用できたため mdb でも困ることはなかったのかも知れませんが、ここ数年で 64 bit OSが主流に切り替わりましたからね。

業務システムに関わっていると、技術の進歩が遅いんだよね。外部からの圧力がないと切り替わらないもんです。