ACCESS を .NET Core の EF Coreでアクセスする

いまさら ACCESS を扱うにしても ADO.NET の DataSet や DataTable を使いたくない場合があります。素直に .NET Framework のほうを使ってもいいのですが、.NET5(多分 .NET6でも大丈夫)で ACCESS のデータベースファイル(*.mdb)を扱います。

EntityFrameworkCore.Jet: Entity Framework Core provider for Access database を使います。

ちょっとだけ難点があって、EF Core の 3.x(最新は EF Core 5.x)だけ対応しています。なので EF Core のバージョンをひとつ前に戻さないといけないのですが、DataSet を使ってちまちまとカラム名を入力するよりは楽です。現在のプロジェクトだと以下のようになります。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
	<ItemGroup>
		<PackageReference Include="EntityFrameworkCore.Jet.OleDb" Version="3.1.0" />
		<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.20" />
	</ItemGroup>
</Project>

これは WPF のプロジェクトなのですが、webapi も作れます。

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net5.0-windows</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
	<PackageReference Include="EntityFrameworkCore.Jet.OleDb" Version="3.1.0" />
	<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.20" />
  </ItemGroup>
</Project>

webapi の場合、.NET5 で作ると TargetFramework が「net5.0」となっていますが「net5.0-windows」のように書き換えます。OLEDB 関係が windows 上のみの動作のため EntityFrameworkCore.Jet.OleDb の制限です。ちなみに net5.0 のままでも、警告はでますが EntityFrameworkCore.Jet.OleDb のクラスやメソッドは利用できます。

Microsoft.ACE.OLEDB.12.0 をインストール

実は Windows 10 が素の状態だと、ACCESS 用の OLE ドライバーが入ってません。あと、最新版の ACCESS の場合、Microsoft.ACE.OLEDB.16.0 だと、EntityFrameworkCore.Jet.OleDb がうまく動かなかったので 12.0 のほうを入れます。

Download Microsoft Access データベース エンジン 2010 再頒布可能コンポーネント from Official Microsoft Download Center
https://www.microsoft.com/ja-jp/download/details.aspx?id=13255

うちの Windows 10 の環境では 32bit 版の Office 2019 が入っていて、あとから Microsoft.ACE.OLEDB.16.0 の再配布コンポーネントがなぜか 32/64 のどちらも入れることができません。16.0 のほうは微妙にややこしいことになるかも。Microsoft.ACE.OLEDB.16.0のインストールについて

DbContext を作る

データベースにアクセスするための DbContext を作ります。これは EF Core の DbContext の作り方と同じです。OnModelCreating をオーバーライドして、HasNoKey を設定しているのは ACCESS データから読み取りしかしないからです。元テーブル(予約テーブルとかには Key が入っていません)。

public class AccessDbContext : DbContext
{
    public AccessDbContext()
    {

    }
    public AccessDbContext(DbContextOptions<AccessDbContext> options) : base(options)
    {

    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        OleDbConnectionStringBuilder builder = new OleDbConnectionStringBuilder();
        builder.Provider = "Microsoft.ACE.OLEDB.12.0";
        builder.DataSource = @"ACCESSファイル.mdb";
        string cnstr = builder.ConnectionString;
        optionsBuilder.UseJetOleDb(cnstr);
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<T_予約>().HasNoKey();
        modelBuilder.Entity<T_予約sub>().HasNoKey();
        modelBuilder.Entity<T_顧客>().HasNoKey();
...

    }
    public DbSet<T_予約> T_予約 { get; set; }
    public DbSet<T_予約sub> T_予約sub { get; set; }
    public DbSet<T_顧客> T_顧客 { get; set; }
...
}

対応するテーブルの作り方もコードファーストのデータベース作りと同じになります。やたらに Nullable が多いのと、カラム名が日本語になっているのが元データが ACCESS のためです。元の ACCESS では VBA でガシガシと組んでいて大変そうです。

public partial class T_予約
{
    public int KID { get; set; }
    public Nullable<int> TID { get; set; }
    public Nullable<int> 予約者 { get; set; }
    public Nullable<int> TaID { get; set; }
    public Nullable<System.DateTime> 日付 { get; set; }
    public Nullable<System.DateTime> 記録日 { get; set; }
    public Nullable<System.DateTime> 契約日 { get; set; }
    public Nullable<int> 予約 { get; set; }
...
}

ACCESS のカラム名は結構自由に作れるので、郵便番号の記号「〒」も使えます。ただし、C# の場合は「〒」をプロパティ名としては使えないので Column 属性で別名をあてておきます。

public partial class T_顧客Sub
{
    public int TIDa { get; set; }
    public Nullable<int> TID { get; set; }
    public string 部署名 { get; set; }
    public string TEL { get; set; }
    public string FAX { get; set; }
    public string 携帯 { get; set; }
    [Column("〒")]
    public string ZIP { get; set; }
    public string 住所1 { get; set; }
    public string 住所2 { get; set; }
...
}

ここまでできあがると、通常の EF Core と同じように ACCESS にアクセスができます。あまり無茶をすると ACCESS ファイルが壊れそうな気がするのですが、まあ SELECT だけならば大丈夫でしょう。

webapi のコントローラーで使う

LINQ 使って記述ができるので、非常に楽です。

/// <summary>
/// 日付を指定して取得する
/// </summary>
/// <param name="date"></param>
/// <returns></returns>
[HttpGet("date/{date}")]
public IEnumerable<T_予約> Get(string date)
{
    DateTime dt = DateTime.Parse(date);
    var items =  _context.T_予約
        .Where( t => t.日付 == dt.Date )
        .ToList();
    foreach ( var item in items )
    {
        item.予約Sub = _context.T_予約sub.Where(t => t.KID == item.KID)
            .OrderBy(t => t.Sort)
            .ToList();
        item.顧客 = _context.T_顧客.FirstOrDefault(t => t.TID == item.TID);
        item.顧客Sub = _context.T_顧客Sub.FirstOrDefault(t => t.TIDa == item.TIDa);
        item.会議室Item = _context.T_ROOM.FirstOrDefault(t => t.HID == item.会議室);
    }
    return items;
}

付属するデータは、LINQ の場合は Include を使って取ってきてもよいのですが、外部キーの記述が結構面倒(標準にあっていないとうまくいかないことが多い)ので、あとから手作業でとってきています。多少検索スピードは落ちますが、ツール的にはこれで十分でしょう。まじめに作るときは SQL Server 等にデータを移行&カラム名を連携しやすいように直します。

ACCESSファイルのロック

ACCESSの場合、テーブル単位ロックがかかっています。例えば、上記の場合、ACCESS 本体で「予約」テーブルを開いていると(デザインを開くも含む)と、EF Core 側で「予約」テーブルを開くことができなくてエラーになります。実際には、ロック解除待ちのタイムアウトになります。

なので、同時利用をしていると変なことになりそうなのですが、そこは運用次第ということで。ひとまず、ACCESS から SQL Server への移行ツールを作るときに EntityFrameworkCore.Jet が便利です。

カテゴリー: 開発 パーマリンク