C#でAccessデータベースに接続・操作する方法|初心者向けにCRUDまで完全解説

はじめに

C#からAccessデータベースへ接続すると、テーブルに保存されたデータの検索、登録、更新、削除をWindowsアプリから実行できます。顧客管理、在庫管理、社内ツール、既存のAccess資産を利用した小規模システムなどで使われる構成です。

C#とAccessを連携する方法はいくつかありますが、初心者が最初に覚えるなら、System.Data.OleDbを利用する方法が分かりやすいでしょう。本記事では、Accessファイルへの接続方法から、SELECT・INSERT・UPDATE・DELETEによるCRUD操作、DataGridViewへの表示、エラー対策まで順番に解説します。

サンプルコードは主にWindowsフォームアプリを想定していますが、接続やCRUD処理の考え方はコンソールアプリやWPFアプリでも共通です。

1. C#でAccessデータベースを扱う前に知っておきたい基礎

1-1. C#からAccessに接続してできること

C#からAccessへ接続すると、主に次のような処理を実装できます。

  • テーブルのデータを一覧表示する

  • IDや商品名を指定して検索する

  • 新しいレコードを登録する

  • 既存レコードを更新する

  • 不要なレコードを削除する

  • 集計結果を画面や帳票に表示する

  • 複数の登録・更新処理をトランザクションでまとめる

  • Accessのテーブル一覧やカラム情報を取得する

Accessを画面操作する代わりに、業務に必要な機能だけをC#アプリとして提供することも可能です。

1-2. Accessデータベース(.accdb/.mdb)とは

Accessデータベースは、Microsoft Accessで作成・編集できるファイル型のデータベースです。代表的な拡張子には、次の2種類があります。

  • .accdb:Access 2007以降で一般的に使われる形式

  • .mdb:Access 2003以前から使われている旧形式

AccessはSQL Serverのように常時稼働するデータベースサーバーへ接続する方式ではなく、基本的にはデータベースファイルを直接読み書きします。

導入しやすい一方、Accessデータベースファイルの最大サイズは、システムオブジェクト用の領域を除いて2GBです。そのため、大量データを長期間蓄積するシステムには適していません。マイクロソフトサポート

1-3. C#とAccessを連携する主な方法

C#からAccessを操作する代表的な方法は次のとおりです。

  • System.Data.OleDbを利用する

  • ODBCドライバーを利用する

  • Entity Frameworkに対応する外部プロバイダーを利用する

  • AccessのCOMオートメーションを利用する

単純なデータの取得や更新であれば、System.Data.OleDbを利用する方法が最も直接的です。

COMオートメーションは、Accessアプリケーション自体を起動してフォームやレポートを操作したい場合に使われます。データベースのCRUD操作だけが目的なら、通常はOleDbを選択します。

1-4. 初心者はOleDbを使うのが基本

System.Data.OleDbには、Accessとの接続やSQL実行に必要なクラスが用意されています。

主に使用するクラスは次のとおりです。

  • OleDbConnection:Accessとの接続を管理する

  • OleDbCommand:SQLを実行する

  • OleDbDataReader:検索結果を1行ずつ読み込む

  • OleDbDataAdapter:検索結果をDataTableへ格納する

  • OleDbParameter:SQLへ値を安全に渡す

  • OleDbTransaction:複数処理をひとまとまりとして管理する

OleDbConnectionは、OLE DBデータソースへの接続を表すクラスです。接続後は明示的に閉じるか、using文で確実に破棄する必要があります。Microsoft Learn+1

1-5. この記事で作成するサンプルアプリの完成イメージ

本記事では、次の項目を持つ商品管理アプリを例にします。

  • 商品ID

  • 商品名

  • 価格

  • 在庫数

  • 更新日時

  • 有効フラグ

画面には商品一覧を表示するDataGridViewと、商品情報を入力するTextBox、NumericUpDown、DateTimePicker、CheckBoxを配置します。

各ボタンを押すと、次の処理を実行します。

  • 一覧表示ボタン:Accessから商品データを取得

  • 登録ボタン:新しい商品を追加

  • 更新ボタン:選択中の商品を更新

  • 削除ボタン:選択中の商品を削除

2. C#でAccessに接続するための準備

2-1. 必要な開発環境

基本的には、次の環境を準備します。

  • Windows 10またはWindows 11

  • Visual Studio

  • .NET Framework、.NET 6、.NET 8など

  • Accessデータベースファイル

  • Microsoft ACE OLE DBプロバイダー

Access本体がインストールされていなくても、対応するAccess Runtimeやデータベースエンジンが導入されていれば、C#からAccessファイルを操作できる場合があります。

.NET FrameworkではSystem.Data.OleDbを標準参照として使用できます。.NET 6や.NET 8では、プロジェクトの構成に応じてNuGetからSystem.Data.OleDbパッケージを追加します。System.Data.OleDbはOLE DBデータソースへアクセスするためのデータプロバイダーです。NuGet+1

Visual Studioのパッケージマネージャーコンソールを使う場合は、次のようにインストールできます。

PowerShell
Install-Package System.Data.OleDb

.NET CLIでは、次のコマンドを使用します。

Bash
dotnet add package System.Data.OleDb

2-2. Access Database Engine/ACE OLEDBプロバイダーとは

ACE OLE DBプロバイダーは、C#などAccess以外のアプリケーションからAccessデータベースへ接続するためのコンポーネントです。

接続文字列では、次のようなProvider名を指定します。

Microsoft.ACE.OLEDB.12.0

または、環境によって次を使用します。

Microsoft.ACE.OLEDB.16.0

Accessや対応するMicrosoft 365製品がインストールされている環境では、ACEプロバイダーがすでに利用できる場合があります。

Officeがインストールされていない環境では、Microsoft 365 Access Runtimeなどの追加コンポーネントが必要になることがあります。Microsoft 365 Access Runtimeには32ビット版と64ビット版が用意されています。Microsoft Learn+1

2-3. 32bit/64bitの違いとビルド設定の注意点

Access接続で特に多い問題が、アプリケーションとACEプロバイダーのビット数不一致です。

例えば、次の組み合わせでは接続できません。

  • C#アプリ:64ビット

  • ACEプロバイダー:32ビット

または、その反対です。

Visual Studioでは、プロジェクトのビルド設定を次のように確認します。

  1. プロジェクトのプロパティを開く

  2. 「ビルド」を選択する

  3. 「プラットフォームターゲット」を確認する

  4. ACEに合わせてx86またはx64を選択する

32ビット版ACEを利用している場合は、C#アプリをx86に設定します。64ビット版ACEを利用している場合は、x64に設定します。

Any CPUは実行環境によって32ビットまたは64ビットとして動作するため、Access接続では問題の原因を特定しにくくなることがあります。最初はx86またはx64を明示的に指定するのがおすすめです。

2-4. .accdbと.mdbで使用するProviderの違い

.accdbファイルでは、基本的にACEプロバイダーを使用します。

Provider=Microsoft.ACE.OLEDB.12.0;

または次のProviderです。

Provider=Microsoft.ACE.OLEDB.16.0;

旧形式の.mdbファイルでは、32ビット環境に限り、次のJetプロバイダーを利用できる場合があります。

Provider=Microsoft.Jet.OLEDB.4.0;

ただし、Jet OLE DB 4.0は古い32ビット向けのプロバイダーです。新しいアプリでは、.mdbに対してもACEプロバイダーを利用したほうが環境を統一しやすくなります。

重要なのは、Accessファイルの拡張子だけでなく、実行環境に対象Providerが登録されていることです。

2-5. サンプル用Accessデータベースとテーブルを作成する

AccessでSample.accdbを作成し、Productsテーブルを追加します。

テーブル設計は次のようにします。

フィールド名Accessのデータ型説明
IDオートナンバー型主キー
ProductName短いテキスト商品名
Price通貨型価格
Stock数値型・長整数在庫数
UpdatedAt日付/時刻型更新日時
IsActiveYes/No型有効フラグ

IDは主キーに設定してください。

作成したSample.accdbは、プロジェクト内のDataフォルダーへ配置します。Visual Studioのファイルプロパティでは、必要に応じて「出力ディレクトリにコピー」を「新しい場合はコピーする」に設定します。

3. C#からAccessへ接続する基本手順

3-1. System.Data.OleDbを使用する

C#ファイルの先頭に、次の名前空間を追加します。

C#
using System.Data;
using System.Data.OleDb;
using System.IO;

.NET 6や.NET 8でOleDbConnectionが見つからない場合は、System.Data.OleDbパッケージが追加されているか確認してください。

3-2. OleDbConnectionとは

OleDbConnectionは、C#アプリとAccessデータベースとの接続を管理するクラスです。

基本的な流れは次のとおりです。

  1. 接続文字列を作成する

  2. OleDbConnectionを生成する

  3. Openメソッドで接続する

  4. SQLを実行する

  5. 接続を閉じる

C#
OleDbConnection connection =
new OleDbConnection(connectionString);

connection.Open();

// データベース処理

connection.Close();

ただし、途中で例外が発生するとCloseまで到達しない可能性があります。そのため、実際のコードではusing文を使用します。

3-3. 接続文字列の書き方

.accdbファイルへ接続する基本的な接続文字列は次のとおりです。

C#
string connectionString =
@"Provider=Microsoft.ACE.OLEDB.12.0;" +
@"Data Source=C:\Database\Sample.accdb;";

ACE 16.0が利用できる環境では、次のように指定できます。

C#
string connectionString =
@"Provider=Microsoft.ACE.OLEDB.16.0;" +
@"Data Source=C:\Database\Sample.accdb;";

接続文字列の主な項目は次の2つです。

  • Provider:利用するOLE DBプロバイダー

  • Data Source:Accessファイルの場所

OleDbConnectionは、接続文字列のProvider=に設定されたOLE DBプロバイダーを利用します。Microsoft Learn+1

3-4. Accessファイルのパス指定方法

絶対パスを直接指定すると、別のパソコンへアプリを配布したときに動作しなくなる可能性があります。

実行ファイルを基準にパスを作成すると、環境が変わっても管理しやすくなります。

C#
string dbPath = Path.Combine(
AppContext.BaseDirectory,
"Data",
"Sample.accdb");

string connectionString =
$@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={dbPath};";

接続前にファイルの存在を確認することも重要です。

C#
if (!File.Exists(dbPath))
{
throw new FileNotFoundException(
"Accessデータベースが見つかりません。",
dbPath);
}

アプリをProgram Files配下へインストールする場合、実行フォルダーにあるAccessファイルへ書き込めないことがあります。書き込みが必要なデータベースは、ユーザーのアプリケーションデータフォルダーや、書き込み権限のある共有フォルダーへ配置してください。

3-5. using文で安全に接続を閉じる

OleDbConnectionIDisposableを実装しているため、using文を利用できます。

C#
using (OleDbConnection connection =
new OleDbConnection(connectionString))
{
connection.Open();

// データベース処理
}

処理中に例外が発生しても、usingブロックを抜けるとDisposeが実行され、接続が解放されます。

OleDbCommandOleDbDataReaderも同様にusingで管理しましょう。

3-6. 接続できるか確認する最小コード

次のコードで、Accessへ接続できるか確認できます。

C#
using System;
using System.Data.OleDb;
using System.IO;

public class ConnectionTest
{
public static void Test()
{
string dbPath = Path.Combine(
AppContext.BaseDirectory,
"Data",
"Sample.accdb");

if (!File.Exists(dbPath))
{
throw new FileNotFoundException(
"データベースファイルがありません。",
dbPath);
}

string connectionString =
$@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={dbPath};";

try
{
using (OleDbConnection connection =
new OleDbConnection(connectionString))
{
connection.Open();
Console.WriteLine("Accessへの接続に成功しました。");
}
}
catch (OleDbException ex)
{
Console.WriteLine($"Access接続エラー: {ex.Message}");
}
}
}

接続に成功しない場合は、Provider名、Accessファイルのパス、ビット数の3点を最初に確認してください。

4. C#でAccessのデータを取得する方法

4-1. SELECT文でデータを取得する基本

Accessからデータを取得するには、SELECT文を使用します。

SQL
SELECT ID, ProductName, Price, Stock, UpdatedAt, IsActive
FROM Products

すべてのカラムを取得するSELECT *も使用できますが、実際のアプリでは必要なカラムを明示するほうが安全です。

4-2. OleDbCommandでSQLを実行する

SQLを実行するには、OleDbCommandを使用します。

C#
string sql =
@"SELECT ID, ProductName, Price, Stock, UpdatedAt, IsActive
FROM Products";

using (OleDbConnection connection =
new OleDbConnection(connectionString))
using (OleDbCommand command =
new OleDbCommand(sql, connection))
{
connection.Open();

// commandを実行する
}

OleDbCommandには、実行するSQLと利用するOleDbConnectionを渡します。

4-3. OleDbDataReaderで1行ずつ読み込む

取得件数が多い場合や、結果を1行ずつ処理したい場合はOleDbDataReaderを利用します。

C#
string sql =
@"SELECT ID, ProductName, Price, Stock
FROM Products
ORDER BY ID";

using (OleDbConnection connection =
new OleDbConnection(connectionString))
using (OleDbCommand command =
new OleDbCommand(sql, connection))
{
connection.Open();

using (OleDbDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
int id = Convert.ToInt32(reader["ID"]);
string productName =
Convert.ToString(reader["ProductName"]) ?? string.Empty;
decimal price = Convert.ToDecimal(reader["Price"]);
int stock = Convert.ToInt32(reader["Stock"]);

Console.WriteLine(
$"{id}: {productName} / {price}円 / 在庫{stock}");
}
}
}

NULLの可能性があるカラムは、IsDBNullで確認します。

C#
DateTime? updatedAt = reader.IsDBNull(
reader.GetOrdinal("UpdatedAt"))
? null
: Convert.ToDateTime(reader["UpdatedAt"]);

4-4. DataTableに取得結果を格納する

DataGridViewへ表示する場合は、OleDbDataAdapterDataTableへ読み込む方法が便利です。

C#
public DataTable GetProducts()
{
DataTable table = new DataTable();

string sql =
@"SELECT ID, ProductName, Price, Stock, UpdatedAt, IsActive
FROM Products
ORDER BY ID";

using (OleDbConnection connection =
new OleDbConnection(connectionString))
using (OleDbDataAdapter adapter =
new OleDbDataAdapter(sql, connection))
{
adapter.Fill(table);
}

return table;
}

OleDbDataAdapterFillメソッドは、必要に応じて接続を開閉し、結果をDataTableへ格納します。

4-5. WHERE句で条件を指定して検索する

IDを指定して1件だけ取得する場合は、WHERE句を使用します。

C#
string sql =
@"SELECT ID, ProductName, Price, Stock, UpdatedAt, IsActive
FROM Products
WHERE ID = ?";

パラメーターを設定して実行します。

C#
using (OleDbConnection connection =
new OleDbConnection(connectionString))
using (OleDbCommand command =
new OleDbCommand(sql, connection))
{
command.Parameters.Add(
"?", OleDbType.Integer).Value = productId;

connection.Open();

using (OleDbDataReader reader = command.ExecuteReader())
{
if (reader.Read())
{
Console.WriteLine(reader["ProductName"]);
}
}
}

商品名の部分一致検索は、次のように実装できます。

C#
string sql =
@"SELECT ID, ProductName, Price, Stock
FROM Products
WHERE ProductName LIKE ?
ORDER BY ID";

command.Parameters.Add(
"?", OleDbType.VarWChar, 100).Value =
$"%{keyword}%";

4-6. ORDER BYで並び替える

価格の高い順に取得する場合は、次のSQLを使用します。

SQL
SELECT ID, ProductName, Price, Stock
FROM Products
ORDER BY Price DESC

複数条件で並び替えることもできます。

SQL
SELECT ID, ProductName, Price, Stock
FROM Products
ORDER BY IsActive DESC, ProductName ASC

ユーザーが指定した文字列を、そのままORDER BY句へ連結してはいけません。並び替え対象は、アプリ側で許可したカラム名から選択するようにします。

C#
string sortColumn = selectedSort switch
{
"価格" => "Price",
"在庫" => "Stock",
"商品名" => "ProductName",
_ => "ID"
};

4-7. DataGridViewにAccessのデータを表示する

WindowsフォームのDataGridViewへ表示するコードは次のとおりです。

C#
private void LoadProducts()
{
try
{
DataTable table = repository.GetAllProducts();
dataGridViewProducts.AutoGenerateColumns = true;
dataGridViewProducts.DataSource = table;
}
catch (Exception ex)
{
MessageBox.Show(
$"一覧の読み込みに失敗しました。\n{ex.Message}",
"エラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}

フォームのLoadイベントから呼び出せば、画面表示時に一覧を読み込めます。

C#
private void MainForm_Load(
object sender,
EventArgs e)
{
LoadProducts();
}

5. C#でAccessにデータを追加する方法

5-1. INSERT文の基本

新しい商品を登録するINSERT文は次のとおりです。

SQL
INSERT INTO Products
(ProductName, Price, Stock, UpdatedAt, IsActive)
VALUES
(?, ?, ?, ?, ?)

IDはオートナンバー型のため、INSERT対象には含めません。

5-2. OleDbCommandで新規レコードを登録する

登録処理では、ExecuteNonQueryを使用します。

C#
public int InsertProduct(
string productName,
decimal price,
int stock,
DateTime updatedAt,
bool isActive)
{
string sql =
@"INSERT INTO Products
(ProductName, Price, Stock, UpdatedAt, IsActive)
VALUES
(?, ?, ?, ?, ?)";

using (OleDbConnection connection =
new OleDbConnection(connectionString))
using (OleDbCommand command =
new OleDbCommand(sql, connection))
{
command.Parameters.Add(
"?", OleDbType.VarWChar, 100).Value = productName;

command.Parameters.Add(
"?", OleDbType.Currency).Value = price;

command.Parameters.Add(
"?", OleDbType.Integer).Value = stock;

command.Parameters.Add(
"?", OleDbType.Date).Value = updatedAt;

command.Parameters.Add(
"?", OleDbType.Boolean).Value = isActive;

connection.Open();
return command.ExecuteNonQuery();
}
}

戻り値は、処理によって影響を受けた行数です。通常、登録に成功すれば1になります。

5-3. パラメーターを使って安全に値を渡す

次のように入力値をSQLへ直接連結するコードは避けてください。

C#
string sql =
"INSERT INTO Products (ProductName) VALUES ('" +
productName +
"')";

商品名にシングルクォートが含まれているとSQLエラーが発生します。また、入力値によってSQLの意味を変更される可能性があります。

必ずパラメーターを使用します。

C#
string sql =
"INSERT INTO Products (ProductName) VALUES (?)";

command.Parameters.Add(
"?", OleDbType.VarWChar, 100).Value =
productName;

OleDbでテキスト形式のSQLを実行する場合は、?による位置パラメーターを使用します。パラメーターは、SQL内に記述された?と同じ順番で追加しなければなりません。Microsoft Learn+2Microsoft Learn+2

5-4. 文字列・数値・日付型の登録時の注意点

文字列にはVarWChar、整数にはInteger、金額にはCurrency、日付にはDate、Yes/No型にはBooleanを指定できます。

C#
command.Parameters.Add(
"?", OleDbType.VarWChar, 100).Value = productName;

command.Parameters.Add(
"?", OleDbType.Integer).Value = stock;

command.Parameters.Add(
"?", OleDbType.Currency).Value = price;

command.Parameters.Add(
"?", OleDbType.Date).Value = updatedAt;

command.Parameters.Add(
"?", OleDbType.Boolean).Value = isActive;

AddWithValueは手軽ですが、値から推測された型がAccess側の型と一致しないことがあります。保守性を高めるため、OleDbTypeと文字列の長さを明示する方法がおすすめです。

空文字列とNULLは異なります。値が存在しないことを表す場合は、DBNull.Valueを渡します。

C#
command.Parameters.Add(
"?", OleDbType.VarWChar, 100).Value =
string.IsNullOrWhiteSpace(note)
? DBNull.Value
: note;

5-5. 登録後に一覧を再読み込みする処理

登録ボタンでは、入力チェック、登録、一覧再読み込みの順番で処理します。

C#
private void buttonInsert_Click(
object sender,
EventArgs e)
{
string productName = textBoxProductName.Text.Trim();

if (productName.Length == 0)
{
MessageBox.Show("商品名を入力してください。");
return;
}

int affectedRows = repository.InsertProduct(
productName,
numericUpDownPrice.Value,
Decimal.ToInt32(numericUpDownStock.Value),
dateTimePickerUpdatedAt.Value,
checkBoxIsActive.Checked);

if (affectedRows == 1)
{
MessageBox.Show("商品を登録しました。");
LoadProducts();
ClearInputs();
}
else
{
MessageBox.Show("商品を登録できませんでした。");
}
}

6. C#でAccessのデータを更新する方法

6-1. UPDATE文の基本

既存の商品情報を更新するSQLは次のとおりです。

SQL
UPDATE Products
SET ProductName = ?,
Price = ?,
Stock = ?,
UpdatedAt = ?,
IsActive = ?
WHERE ID = ?

WHERE句を省略すると、テーブル内のすべてのレコードが更新されます。更新処理では必ず対象を限定してください。

6-2. 主キーを指定して対象レコードを更新する

更新メソッドは次のように実装できます。

C#
public int UpdateProduct(
int id,
string productName,
decimal price,
int stock,
DateTime updatedAt,
bool isActive)
{
string sql =
@"UPDATE Products
SET ProductName = ?,
Price = ?,
Stock = ?,
UpdatedAt = ?,
IsActive = ?
WHERE ID = ?";

using (OleDbConnection connection =
new OleDbConnection(connectionString))
using (OleDbCommand command =
new OleDbCommand(sql, connection))
{
command.Parameters.Add(
"?", OleDbType.VarWChar, 100).Value = productName;

command.Parameters.Add(
"?", OleDbType.Currency).Value = price;

command.Parameters.Add(
"?", OleDbType.Integer).Value = stock;

command.Parameters.Add(
"?", OleDbType.Date).Value = updatedAt;

command.Parameters.Add(
"?", OleDbType.Boolean).Value = isActive;

command.Parameters.Add(
"?", OleDbType.Integer).Value = id;

connection.Open();
return command.ExecuteNonQuery();
}
}

6-3. OleDbParameterの順番に注意する

OleDbでは、パラメーター名より追加順序が重要です。

例えば、SQLの順番が次のようになっているとします。

SQL
UPDATE Products
SET ProductName = ?, Price = ?
WHERE ID = ?

パラメーターも同じ順番で追加します。

C#
command.Parameters.Add(
"?", OleDbType.VarWChar, 100).Value = productName;

command.Parameters.Add(
"?", OleDbType.Currency).Value = price;

command.Parameters.Add(
"?", OleDbType.Integer).Value = id;

IDを最初に追加すると、IDの値がProductNameへ渡されるなど、型不一致や誤更新の原因になります。

コード上で@ProductNameのような名前を付けても、OleDbのテキストSQLでは基本的に位置関係で処理されます。SQL内には?を記述し、追加順を揃えると混乱を防げます。

6-4. DataGridViewで選択した行を更新する

DataGridViewの選択行からIDを取得して更新します。

C#
private int? GetSelectedProductId()
{
if (dataGridViewProducts.CurrentRow == null)
{
return null;
}

object value =
dataGridViewProducts.CurrentRow.Cells["ID"].Value;

if (value == null || value == DBNull.Value)
{
return null;
}

return Convert.ToInt32(value);
}

更新ボタンの処理は次のようになります。

C#
private void buttonUpdate_Click(
object sender,
EventArgs e)
{
int? id = GetSelectedProductId();

if (!id.HasValue)
{
MessageBox.Show("更新する商品を選択してください。");
return;
}

string productName = textBoxProductName.Text.Trim();

if (productName.Length == 0)
{
MessageBox.Show("商品名を入力してください。");
return;
}

int affectedRows = repository.UpdateProduct(
id.Value,
productName,
numericUpDownPrice.Value,
Decimal.ToInt32(numericUpDownStock.Value),
dateTimePickerUpdatedAt.Value,
checkBoxIsActive.Checked);

if (affectedRows == 1)
{
MessageBox.Show("商品を更新しました。");
LoadProducts();
}
else
{
MessageBox.Show(
"更新対象が見つからないか、更新できませんでした。");
}
}

6-5. 更新件数を確認して処理結果を判定する

ExecuteNonQueryの戻り値を確認すると、更新対象が存在したかを判定できます。

C#
int affectedRows = command.ExecuteNonQuery();

if (affectedRows == 1)
{
// 更新成功
}
else if (affectedRows == 0)
{
// 対象のIDが存在しない
}
else
{
// 想定以上の件数が更新された
}

主キーをWHERE条件にしている場合、通常は0または1です。複数件が返る場合は、WHERE条件や主キー設定を確認してください。

7. C#でAccessのデータを削除する方法

7-1. DELETE文の基本

レコードを削除するSQLは次のとおりです。

SQL
DELETE FROM Products
WHERE ID = ?

UPDATEと同様、WHERE句を省略するとすべてのレコードが削除されます。

7-2. IDを指定してレコードを削除する

削除メソッドは次のように実装します。

C#
public int DeleteProduct(int id)
{
string sql =
@"DELETE FROM Products
WHERE ID = ?";

using (OleDbConnection connection =
new OleDbConnection(connectionString))
using (OleDbCommand command =
new OleDbCommand(sql, connection))
{
command.Parameters.Add(
"?", OleDbType.Integer).Value = id;

connection.Open();
return command.ExecuteNonQuery();
}
}

7-3. 削除前に確認メッセージを表示する

削除は元に戻せないため、実行前に確認メッセージを表示します。

C#
DialogResult result = MessageBox.Show(
"選択した商品を削除しますか?",
"削除確認",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2);

if (result != DialogResult.Yes)
{
return;
}

初期選択を「いいえ」にしておくと、Enterキーの誤操作による削除を防ぎやすくなります。

7-4. 誤削除を防ぐための実装ポイント

誤削除を防ぐには、次の対策が効果的です。

  • 主キーを必ずWHERE条件にする

  • IDが取得できない場合は処理を中止する

  • 削除前に商品名も表示する

  • 確認ダイアログの初期選択を「いいえ」にする

  • 削除件数が1件であることを確認する

  • 重要データは物理削除ではなく論理削除にする

  • Accessファイルを定期的にバックアップする

論理削除では、レコード自体を削除せず、IsActiveなどのフラグをFalseに更新します。

SQL
UPDATE Products
SET IsActive = False
WHERE ID = ?

履歴を残したいシステムでは、物理削除より論理削除が適しています。

7-5. 削除後に一覧を更新する

削除ボタンのコード例は次のとおりです。

C#
private void buttonDelete_Click(
object sender,
EventArgs e)
{
int? id = GetSelectedProductId();

if (!id.HasValue)
{
MessageBox.Show("削除する商品を選択してください。");
return;
}

string productName =
Convert.ToString(
dataGridViewProducts
.CurrentRow
.Cells["ProductName"]
.Value)
?? string.Empty;

DialogResult result = MessageBox.Show(
$"「{productName}」を削除しますか?",
"削除確認",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2);

if (result != DialogResult.Yes)
{
return;
}

int affectedRows = repository.DeleteProduct(id.Value);

if (affectedRows == 1)
{
MessageBox.Show("商品を削除しました。");
LoadProducts();
ClearInputs();
}
else
{
MessageBox.Show(
"削除対象が見つからないか、削除できませんでした。");
}
}

8. C#でAccessをCRUD操作するサンプルコード

8-1. CRUD処理の全体構成

サンプルアプリは、次のような構成にします。

AccessCrudSample
├─ Data
│ └─ Sample.accdb
├─ Models
│ └─ Product.cs
├─ DataAccess
│ └─ ProductRepository.cs
├─ MainForm.cs
└─ Program.cs

画面にSQL処理をすべて書くのではなく、データベース処理をProductRepositoryへ分離します。

8-2. 接続処理を共通化する

商品モデルを作成します。

C#
public class Product
{
public int ID { get; set; }

public string ProductName { get; set; } =
string.Empty;

public decimal Price { get; set; }

public int Stock { get; set; }

public DateTime UpdatedAt { get; set; }

public bool IsActive { get; set; }
}

次に、接続処理を共通化したリポジトリクラスを作成します。

C#
using System;
using System.Data;
using System.Data.OleDb;
using System.IO;

public class ProductRepository
{
private readonly string connectionString;

public ProductRepository(string databasePath)
{
if (string.IsNullOrWhiteSpace(databasePath))
{
throw new ArgumentException(
"データベースのパスが指定されていません。",
nameof(databasePath));
}

if (!File.Exists(databasePath))
{
throw new FileNotFoundException(
"Accessデータベースが見つかりません。",
databasePath);
}

connectionString =
$@"Provider=Microsoft.ACE.OLEDB.12.0;" +
$@"Data Source={databasePath};";
}

private OleDbConnection CreateConnection()
{
return new OleDbConnection(connectionString);
}
}

接続を開いた状態のまま返すのではなく、必要なメソッド内で開閉する設計にすると、接続の解放漏れを防ぎやすくなります。

8-3. SELECT処理のサンプルコード

一覧取得メソッドを追加します。

C#
public DataTable GetAllProducts(
string keyword = "")
{
DataTable table = new DataTable();

string sql;

if (string.IsNullOrWhiteSpace(keyword))
{
sql =
@"SELECT
ID,
ProductName,
Price,
Stock,
UpdatedAt,
IsActive
FROM Products
ORDER BY ID";
}
else
{
sql =
@"SELECT
ID,
ProductName,
Price,
Stock,
UpdatedAt,
IsActive
FROM Products
WHERE ProductName LIKE ?
ORDER BY ID";
}

using (OleDbConnection connection =
CreateConnection())
using (OleDbCommand command =
new OleDbCommand(sql, connection))
{
if (!string.IsNullOrWhiteSpace(keyword))
{
command.Parameters.Add(
"?", OleDbType.VarWChar, 100).Value =
$"%{keyword}%";
}

using (OleDbDataAdapter adapter =
new OleDbDataAdapter(command))
{
adapter.Fill(table);
}
}

return table;
}

IDによる1件取得も追加できます。

C#
public Product? GetProductById(int id)
{
string sql =
@"SELECT
ID,
ProductName,
Price,
Stock,
UpdatedAt,
IsActive
FROM Products
WHERE ID = ?";

using (OleDbConnection connection =
CreateConnection())
using (OleDbCommand command =
new OleDbCommand(sql, connection))
{
command.Parameters.Add(
"?", OleDbType.Integer).Value = id;

connection.Open();

using (OleDbDataReader reader =
command.ExecuteReader())
{
if (!reader.Read())
{
return null;
}

return new Product
{
ID = Convert.ToInt32(reader["ID"]),

ProductName =
Convert.ToString(
reader["ProductName"])
?? string.Empty,

Price =
Convert.ToDecimal(reader["Price"]),

Stock =
Convert.ToInt32(reader["Stock"]),

UpdatedAt =
Convert.ToDateTime(
reader["UpdatedAt"]),

IsActive =
Convert.ToBoolean(
reader["IsActive"])
};
}
}
}

8-4. INSERT処理のサンプルコード

C#
public int InsertProduct(Product product)
{
if (product == null)
{
throw new ArgumentNullException(
nameof(product));
}

if (string.IsNullOrWhiteSpace(
product.ProductName))
{
throw new ArgumentException(
"商品名は必須です。",
nameof(product));
}

string sql =
@"INSERT INTO Products
(
ProductName,
Price,
Stock,
UpdatedAt,
IsActive
)
VALUES
(?, ?, ?, ?, ?)";

using (OleDbConnection connection =
CreateConnection())
using (OleDbCommand command =
new OleDbCommand(sql, connection))
{
command.Parameters.Add(
"?", OleDbType.VarWChar, 100).Value =
product.ProductName;

command.Parameters.Add(
"?", OleDbType.Currency).Value =
product.Price;

command.Parameters.Add(
"?", OleDbType.Integer).Value =
product.Stock;

command.Parameters.Add(
"?", OleDbType.Date).Value =
product.UpdatedAt;

command.Parameters.Add(
"?", OleDbType.Boolean).Value =
product.IsActive;

connection.Open();
return command.ExecuteNonQuery();
}
}

8-5. UPDATE処理のサンプルコード

C#
public int UpdateProduct(Product product)
{
if (product == null)
{
throw new ArgumentNullException(
nameof(product));
}

if (product.ID <= 0)
{
throw new ArgumentException(
"有効な商品IDを指定してください。",
nameof(product));
}

string sql =
@"UPDATE Products
SET
ProductName = ?,
Price = ?,
Stock = ?,
UpdatedAt = ?,
IsActive = ?
WHERE ID = ?";

using (OleDbConnection connection =
CreateConnection())
using (OleDbCommand command =
new OleDbCommand(sql, connection))
{
command.Parameters.Add(
"?", OleDbType.VarWChar, 100).Value =
product.ProductName;

command.Parameters.Add(
"?", OleDbType.Currency).Value =
product.Price;

command.Parameters.Add(
"?", OleDbType.Integer).Value =
product.Stock;

command.Parameters.Add(
"?", OleDbType.Date).Value =
product.UpdatedAt;

command.Parameters.Add(
"?", OleDbType.Boolean).Value =
product.IsActive;

command.Parameters.Add(
"?", OleDbType.Integer).Value =
product.ID;

connection.Open();
return command.ExecuteNonQuery();
}
}

8-6. DELETE処理のサンプルコード

C#
public int DeleteProduct(int id)
{
if (id <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(id),
"有効な商品IDを指定してください。");
}

string sql =
@"DELETE FROM Products
WHERE ID = ?";

using (OleDbConnection connection =
CreateConnection())
using (OleDbCommand command =
new OleDbCommand(sql, connection))
{
command.Parameters.Add(
"?", OleDbType.Integer).Value = id;

connection.Open();
return command.ExecuteNonQuery();
}
}

8-7. Windowsフォームアプリでの実装例

フォームクラスでは、リポジトリをフィールドとして保持します。

C#
using System;
using System.Data;
using System.IO;
using System.Windows.Forms;

public partial class MainForm : Form
{
private readonly ProductRepository repository;

public MainForm()
{
InitializeComponent();

string databasePath = Path.Combine(
AppContext.BaseDirectory,
"Data",
"Sample.accdb");

repository =
new ProductRepository(databasePath);
}

private void MainForm_Load(
object sender,
EventArgs e)
{
LoadProducts();
}

private void LoadProducts()
{
string keyword =
textBoxSearch.Text.Trim();

DataTable table =
repository.GetAllProducts(keyword);

dataGridViewProducts.DataSource = table;
}

private Product CreateProductFromInputs()
{
return new Product
{
ProductName =
textBoxProductName.Text.Trim(),

Price =
numericUpDownPrice.Value,

Stock =
Decimal.ToInt32(
numericUpDownStock.Value),

UpdatedAt =
dateTimePickerUpdatedAt.Value,

IsActive =
checkBoxIsActive.Checked
};
}

private void buttonSearch_Click(
object sender,
EventArgs e)
{
ExecuteWithErrorHandling(LoadProducts);
}

private void buttonInsert_Click(
object sender,
EventArgs e)
{
ExecuteWithErrorHandling(() =>
{
Product product =
CreateProductFromInputs();

int count =
repository.InsertProduct(product);

if (count == 1)
{
MessageBox.Show(
"登録しました。");
LoadProducts();
}
});
}

private void buttonUpdate_Click(
object sender,
EventArgs e)
{
ExecuteWithErrorHandling(() =>
{
int? id = GetSelectedProductId();

if (!id.HasValue)
{
MessageBox.Show(
"更新する行を選択してください。");
return;
}

Product product =
CreateProductFromInputs();

product.ID = id.Value;

int count =
repository.UpdateProduct(product);

MessageBox.Show(
count == 1
? "更新しました。"
: "更新対象が見つかりません。");

LoadProducts();
});
}

private void buttonDelete_Click(
object sender,
EventArgs e)
{
ExecuteWithErrorHandling(() =>
{
int? id = GetSelectedProductId();

if (!id.HasValue)
{
MessageBox.Show(
"削除する行を選択してください。");
return;
}

DialogResult result =
MessageBox.Show(
"選択した商品を削除しますか?",
"確認",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2);

if (result != DialogResult.Yes)
{
return;
}

int count =
repository.DeleteProduct(id.Value);

MessageBox.Show(
count == 1
? "削除しました。"
: "削除対象が見つかりません。");

LoadProducts();
});
}

private int? GetSelectedProductId()
{
if (dataGridViewProducts.CurrentRow == null)
{
return null;
}

object value =
dataGridViewProducts
.CurrentRow
.Cells["ID"]
.Value;

return value == null ||
value == DBNull.Value
? null
: Convert.ToInt32(value);
}

private void ExecuteWithErrorHandling(
Action action)
{
try
{
action();
}
catch (Exception ex)
{
MessageBox.Show(
ex.Message,
"エラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
}

DataGridViewのSelectionChangedイベントで、選択した行の値を入力欄へ反映できます。

C#
private void dataGridViewProducts_SelectionChanged(
object sender,
EventArgs e)
{
DataGridViewRow? row =
dataGridViewProducts.CurrentRow;

if (row == null)
{
return;
}

textBoxProductName.Text =
Convert.ToString(
row.Cells["ProductName"].Value)
?? string.Empty;

numericUpDownPrice.Value =
Convert.ToDecimal(
row.Cells["Price"].Value);

numericUpDownStock.Value =
Convert.ToDecimal(
row.Cells["Stock"].Value);

dateTimePickerUpdatedAt.Value =
Convert.ToDateTime(
row.Cells["UpdatedAt"].Value);

checkBoxIsActive.Checked =
Convert.ToBoolean(
row.Cells["IsActive"].Value);
}

9. C#とAccess接続でよくあるエラーと解決方法

9-1. Microsoft.ACE.OLEDB.12.0が登録されていないエラー

代表的なエラーメッセージは次のとおりです。

'Microsoft.ACE.OLEDB.12.0' プロバイダーは
ローカルのコンピューターに登録されていません。

主な原因は次の3つです。

  • ACEプロバイダーがインストールされていない

  • Provider名が環境と合っていない

  • C#アプリとACEのビット数が一致していない

対処方法は次のとおりです。

  1. AccessまたはAccess Runtimeの有無を確認する

  2. ACE 12.0または16.0が利用できるか確認する

  3. プロジェクトをx86またはx64へ変更する

  4. 実行環境に合ったAccess Runtimeを導入する

Microsoftのトラブルシューティング情報でも、Providerが登録されていない場合に、ACE OLE DBデータソースへ接続できないエラーが発生すると説明されています。Microsoft Learn

インストール済みのOLE DBプロバイダーは、次のコードでも確認できます。

C#
DataTable providers =
new OleDbEnumerator().GetElements();

foreach (DataRow row in providers.Rows)
{
Console.WriteLine(
row["SOURCES_NAME"]);
}

9-2. データベースファイルが見つからないエラー

次の点を確認してください。

  • パスに入力ミスがないか

  • 出力フォルダーへAccessファイルがコピーされているか

  • 相対パスの基準が想定どおりか

  • 拡張子が.accdbまたは.mdbになっているか

  • 配布先にDataフォルダーが存在するか

接続前にFile.Existsで確認すると、原因が分かりやすくなります。

C#
if (!File.Exists(databasePath))
{
throw new FileNotFoundException(
$"データベースが見つかりません: {databasePath}");
}

9-3. ファイルが使用中で開けないエラー

Accessはファイル型データベースのため、ファイルのロック状態やフォルダー権限の影響を受けます。

次の点を確認してください。

  • Accessで排他モードとして開かれていないか

  • 別のアプリがファイルを占有していないか

  • フォルダーへ読み書きできるか

  • ロックファイルを作成できる権限があるか

  • ネットワーク接続が切断されていないか

  • OleDbConnectionOleDbDataReaderが解放されているか

タスクマネージャーから強制終了すると、ロック関連のファイルが一時的に残ることがあります。利用者がいないことを確認してから、Accessや対象アプリをすべて終了してください。

9-4. 型が一致しないエラー

「条件式でデータ型が一致しません」などのエラーは、Access側のカラム型とパラメーター型が一致していないときに発生します。

例えば、数値型のIDへ文字列を渡すとエラーになります。

C#
// 避ける
command.Parameters.Add(
"?", OleDbType.VarWChar).Value = "10";

正しくは数値型を指定します。

C#
command.Parameters.Add(
"?", OleDbType.Integer).Value = 10;

Accessのテーブル設計と、C#のOleDbTypeを照合してください。

9-5. SQL構文エラー

SQL構文エラーが発生した場合は、次の点を確認します。

  • カラム名やテーブル名のスペル

  • カンマの不足

  • 丸括弧の不足

  • ?の数とパラメーター数

  • Access SQLで利用できない構文

  • 予約語の使用

  • UPDATEやINSERTのカラム順

まずSQLをAccessのクエリ画面で実行し、構文自体が正しいか確認する方法も有効です。

9-6. 予約語をカラム名に使った場合の対処法

NameDateValueUserなど、SQLやAccessで特別な意味を持つ単語をカラム名にすると、構文エラーの原因になります。

予約語を使用している場合は、角括弧で囲みます。

SQL
SELECT [Name], [Date]
FROM [User]

C#でも同様に記述します。

C#
string sql =
@"SELECT [Name], [Date]
FROM [User]";

可能であれば、ProductNameUpdatedAtなど、予約語と重ならない名前へ変更するのが根本的な対策です。

9-7. 32bit/64bit不一致による接続エラー

同じ接続文字列が別のパソコンでは動作するのに、自分の環境では動作しない場合、ビット数の不一致が疑われます。

確認する項目は次のとおりです。

  • OfficeまたはAccess Runtimeのビット数

  • ACEプロバイダーのビット数

  • C#アプリのプラットフォームターゲット

  • デバッグ実行と公開後の実行設定

例えば、32ビット版Access Runtimeを利用する場合は、プロジェクトをx86でビルドします。

配布先によって32ビット版と64ビット版が混在する場合は、配布要件として利用するACEのビット数を統一すると運用しやすくなります。

10. Access操作を安全・保守しやすくする実装ポイント

10-1. SQLインジェクション対策としてパラメーターを使う

検索条件や登録値をSQLへ直接連結してはいけません。

C#
// 危険な例
string sql =
"SELECT * FROM Products WHERE ProductName = '" +
textBoxProductName.Text +
"'";

次のようにパラメーターを使用します。

C#
string sql =
@"SELECT *
FROM Products
WHERE ProductName = ?";

command.Parameters.Add(
"?", OleDbType.VarWChar, 100).Value =
textBoxProductName.Text;

パラメーターを使用すると、入力値とSQL構文を分離でき、シングルクォートを含む文字列も安全に渡せます。

ただし、テーブル名やカラム名は通常のパラメーターでは置き換えられません。動的に変更する場合は、許可リストから選択してください。

10-2. 接続文字列をコードに直書きしない

サンプルでは分かりやすさのためコード内で接続文字列を作成しましたが、実際のシステムでは設定ファイルから読み込む設計が適しています。

.NET Frameworkでは、App.configconnectionStringsセクションへ保存できます。

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add
name="AccessDatabase"
connectionString="Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Database\Sample.accdb;"
providerName="System.Data.OleDb" />
</connectionStrings>
</configuration>

C#では次のように取得します。

C#
string connectionString =
ConfigurationManager
.ConnectionStrings["AccessDatabase"]
.ConnectionString;

ADO.NETでは、接続文字列をアプリケーション構成ファイルのconnectionStringsセクションに保存できます。Microsoft Learn+1

.NET 6や.NET 8では、appsettings.json、環境変数、独自の設定ファイルなどを利用できます。

データベースにパスワードを設定している場合でも、接続文字列を平文で公開しないよう注意してください。

10-3. 例外処理でエラー内容を確認する

データベース処理では、OleDbExceptionを個別に捕捉できます。

C#
try
{
// Access操作
}
catch (OleDbException ex)
{
foreach (OleDbError error in ex.Errors)
{
Console.WriteLine(
$"エラー番号: {error.NativeError}");
Console.WriteLine(
$"内容: {error.Message}");
}

throw;
}
catch (IOException ex)
{
Console.WriteLine(
$"ファイルエラー: {ex.Message}");
throw;
}

利用者向け画面には分かりやすいメッセージを表示し、詳細なエラーはログに記録する設計が適しています。

接続文字列にパスワードが含まれる場合は、ログへそのまま出力しないでください。

10-4. トランザクションを使うべきケース

複数のSQLをまとめて成功または失敗させたい場合は、トランザクションを使用します。

例えば、注文データを登録した後に在庫数を減らす処理では、片方だけ成功するとデータが不整合になります。

C#
using (OleDbConnection connection =
CreateConnection())
{
connection.Open();

using (OleDbTransaction transaction =
connection.BeginTransaction())
{
try
{
using (OleDbCommand insertCommand =
connection.CreateCommand())
{
insertCommand.Transaction =
transaction;

insertCommand.CommandText =
@"INSERT INTO Orders
(ProductID, Quantity)
VALUES (?, ?)";

insertCommand.Parameters.Add(
"?", OleDbType.Integer).Value =
productId;

insertCommand.Parameters.Add(
"?", OleDbType.Integer).Value =
quantity;

insertCommand.ExecuteNonQuery();
}

using (OleDbCommand updateCommand =
connection.CreateCommand())
{
updateCommand.Transaction =
transaction;

updateCommand.CommandText =
@"UPDATE Products
SET Stock = Stock - ?
WHERE ID = ?";

updateCommand.Parameters.Add(
"?", OleDbType.Integer).Value =
quantity;

updateCommand.Parameters.Add(
"?", OleDbType.Integer).Value =
productId;

int count =
updateCommand.ExecuteNonQuery();

if (count != 1)
{
throw new InvalidOperationException(
"在庫の更新対象が見つかりません。");
}
}

transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}

10-5. NULL値の扱い方

データベースのNULLは、C#のnullではなくDBNull.Valueとして扱います。

登録時は次のように変換します。

C#
object databaseValue =
note == null
? DBNull.Value
: note;

command.Parameters.Add(
"?", OleDbType.VarWChar, 255).Value =
databaseValue;

取得時はIsDBNullを利用します。

C#
int ordinal =
reader.GetOrdinal("Note");

string? note =
reader.IsDBNull(ordinal)
? null
: reader.GetString(ordinal);

DataRowの場合は次のように確認できます。

C#
string? note =
row.IsNull("Note")
? null
: Convert.ToString(row["Note"]);

10-6. 日付型・Yes/No型・通貨型の扱い方

日付は文字列へ変換せず、DateTimeとして渡します。

C#
command.Parameters.Add(
"?", OleDbType.Date).Value =
DateTime.Now;

Yes/No型にはboolを渡します。

C#
command.Parameters.Add(
"?", OleDbType.Boolean).Value =
true;

通貨型にはdecimalを使用します。

C#
decimal price = 1980m;

command.Parameters.Add(
"?", OleDbType.Currency).Value =
price;

金額をdoubleで扱うと、2進浮動小数点による誤差が発生する可能性があります。金額計算にはdecimalを利用しましょう。

10-7. 処理をクラス化して再利用しやすくする

フォーム内へ接続、SQL、入力チェックをすべて書くと、コードが複雑になります。

役割を次のように分離すると保守しやすくなります。

  • フォーム:画面表示とユーザー操作

  • モデル:商品データの保持

  • リポジトリ:AccessへのCRUD処理

  • サービス:業務ルールや複数処理の制御

  • 設定クラス:接続文字列やファイルパスの管理

例えば、商品登録時の在庫チェックや価格チェックは、フォームではなくサービスクラスへ分離できます。

11. C#からAccessを使うメリット・デメリット

11-1. Accessを使うメリット

C#とAccessを組み合わせる主なメリットは次のとおりです。

  • データベースサーバーを構築せずに始められる

  • Accessファイルをコピーしてバックアップできる

  • 小規模な社内ツールを短期間で作りやすい

  • 既存のAccessデータを活用できる

  • Access側からテーブルやデータを確認できる

  • Windows環境では導入しやすい

利用者が少なく、データ量も小さいアプリでは、構成を単純にできる点が大きな利点です。

11-2. Accessを使うデメリット

一方、次のようなデメリットがあります。

  • データベースファイルのサイズに上限がある

  • ファイル破損の影響を受ける可能性がある

  • 多人数の同時更新に向いていない

  • ネットワーク障害の影響を受けやすい

  • WindowsとACEプロバイダーへの依存がある

  • サーバー型データベースほど細かな権限管理ができない

  • Webアプリやクラウド環境で扱いにくい

Accessファイルそのものを取得されると、内部データへアクセスされる可能性があります。機密データを扱う場合は、ファイルのアクセス権限も適切に設定してください。

11-3. 小規模アプリに向いている理由

Accessは、次のような用途に向いています。

  • 個人または少人数で使う業務ツール

  • 数千件から数万件程度のデータ管理

  • 既存のAccess台帳を利用するアプリ

  • オフラインで動作するWindowsアプリ

  • 試作や社内向けの簡易システム

  • 一時的なデータ移行ツール

専用のデータベースサーバーを用意せず、1つのファイルで管理できるため、小規模なアプリを素早く開発できます。

11-4. 複数人利用・大規模運用で注意すべき点

複数人が同じAccessファイルを共有する場合は、次の問題が起きやすくなります。

  • 同時更新による競合

  • ファイルロック

  • ネットワーク切断による処理失敗

  • データベースファイルの破損

  • バックアップ中の競合

  • パフォーマンスの低下

共有フォルダーへAccessファイルを配置するだけの構成は簡単ですが、利用者や更新頻度が増えるほど不安定になりやすくなります。

業務への影響が大きいシステム、24時間稼働するシステム、多数の利用者が同時に更新するシステムでは、SQL Serverなどへの移行を検討してください。

11-5. SQL ServerやSQLiteと比較した選び方

Access、SQL Server、SQLiteは、用途によって使い分けます。

Accessが向いているケースは次のとおりです。

  • 既存のAccessファイルを活用したい

  • Accessでデータを直接確認したい

  • 小規模なWindowsアプリを作りたい

  • 利用者が少ない

SQLiteが向いているケースは次のとおりです。

  • アプリへ組み込みたい

  • AccessやOffice環境へ依存したくない

  • Windows以外でも動かしたい

  • 単一ユーザー中心のローカルアプリを作りたい

SQL Serverが向いているケースは次のとおりです。

  • 複数人が同時利用する

  • データ量が増える予定がある

  • 高い可用性や権限管理が必要

  • Webアプリや社内システムで利用する

  • バックアップや監視を本格的に行いたい

新規開発でAccessを使う理由が特にない場合は、SQLiteやSQL Serverも含めて比較するとよいでしょう。

12. C#とAccess連携に関するよくある質問

12-1. Accessがインストールされていなくても接続できる?

Access本体がインストールされていなくても、実行環境に対応するACE OLE DBプロバイダーがあれば接続できます。

Officeがインストールされていないパソコンでは、Microsoft 365 Access Runtimeなどの導入を検討します。Runtimeには32ビット版と64ビット版があるため、C#アプリのビット数と合わせてください。Microsoft Learn+1

ただし、Access本体がなければ、通常のAccess画面でテーブル設計やクエリ設計を編集することはできません。

12-2. .NET Frameworkと.NET 6/.NET 8で違いはある?

.NET Frameworkでは、System.Data.OleDbをフレームワークの機能として利用できます。

.NET 6や.NET 8では、通常はNuGetからSystem.Data.OleDbパッケージを追加します。

Bash
dotnet add package System.Data.OleDb

接続、SELECT、INSERT、UPDATE、DELETEの基本的なコードはほぼ共通です。ただし、ACE OLE DB自体がWindowsのネイティブコンポーネントであるため、実行環境やビット数への依存は残ります。

新しい.NETで開発する場合でも、配布先にACEプロバイダーが存在するかを確認する必要があります。

12-3. パスワード付きAccessファイルに接続できる?

データベースパスワードが設定されたAccessファイルでは、接続文字列にパスワードを追加できる場合があります。

C#
string connectionString =
$@"Provider=Microsoft.ACE.OLEDB.12.0;" +
$@"Data Source={databasePath};" +
$@"Jet OLEDB:Database Password={password};";

ただし、パスワードをソースコードへ直接記述してはいけません。設定ファイルや安全な資格情報管理の仕組みを利用してください。

また、Accessファイルのパスワードだけを重要データの完全な保護手段として扱うべきではありません。ファイルやフォルダーのアクセス権限も設定する必要があります。

12-4. Accessのテーブル一覧やカラム一覧を取得できる?

GetOleDbSchemaTableを使用すると、テーブルやカラムのスキーマ情報を取得できます。このメソッドは、指定したOLE DBスキーマ情報をDataTableとして返します。Microsoft Learn+1

テーブル一覧の取得例は次のとおりです。

C#
using (OleDbConnection connection =
new OleDbConnection(connectionString))
{
connection.Open();

DataTable? tables =
connection.GetOleDbSchemaTable(
OleDbSchemaGuid.Tables,
new object?[]
{
null,
null,
null,
"TABLE"
});

if (tables != null)
{
foreach (DataRow row in tables.Rows)
{
Console.WriteLine(
row["TABLE_NAME"]);
}
}
}

Productsテーブルのカラム一覧は次のように取得できます。

C#
using (OleDbConnection connection =
new OleDbConnection(connectionString))
{
connection.Open();

DataTable? columns =
connection.GetOleDbSchemaTable(
OleDbSchemaGuid.Columns,
new object?[]
{
null,
null,
"Products",
null
});

if (columns != null)
{
foreach (DataRow row in columns.Rows)
{
Console.WriteLine(
row["COLUMN_NAME"]);
}
}
}

12-5. ExcelのようにAccessを直接編集しながら使える?

Access本体がインストールされていれば、Accessファイルを開いてテーブルを直接編集できます。

ただし、C#アプリとAccessで同じレコードを同時に編集すると、ロックや競合が発生する可能性があります。運用中のデータをAccessから直接変更すると、アプリ側の入力チェックや業務ルールを通らない点にも注意が必要です。

本番データを直接編集する場合は、事前にバックアップを作成し、利用者が少ない時間帯に行うのが安全です。

12-6. WebアプリからAccessを使ってもよい?

技術的に接続できる構成はありますが、通常のWebアプリでAccessをデータベースとして使用することはおすすめできません。

主な理由は次のとおりです。

  • 同時アクセスが増えやすい

  • ファイルロックの影響を受ける

  • Webサーバーの実行権限設定が必要

  • 複数サーバー構成にしにくい

  • WindowsとACEへ依存する

  • データ量やファイルサイズに制約がある

  • 障害時の復旧や監視が難しい

Webアプリでは、SQL Server、Azure SQL Database、PostgreSQL、MySQLなどのサーバー型データベースを利用するほうが適しています。

Accessは、少人数で使うWindowsデスクトップアプリや、既存Accessデータを利用する社内ツールでの使用が現実的です。

まとめ

C#からAccessデータベースへ接続する場合は、System.Data.OleDbとACE OLE DBプロバイダーを使用する方法が基本です。

接続文字列にProviderとAccessファイルのパスを指定し、OleDbConnectionで接続します。SELECTにはExecuteReaderOleDbDataAdapter、INSERT・UPDATE・DELETEにはExecuteNonQueryを使用します。

実装時に特に重要なポイントは次のとおりです。

  • C#アプリとACEプロバイダーの32bit・64bitを合わせる

  • 接続やCommandをusing文で確実に破棄する

  • SQLへ値を直接連結せず、パラメーターを使用する

  • OleDbParameterをSQL内の?と同じ順番で追加する

  • Access側のデータ型とOleDbTypeを一致させる

  • UPDATEとDELETEでは主キーをWHERE条件にする

  • ExecuteNonQueryの戻り値で処理件数を確認する

  • 複数の更新処理にはトランザクションを使用する

  • Accessファイルのパス、権限、バックアップを適切に管理する

  • 利用者やデータ量が増える場合はSQL Serverなどへの移行を検討する

小規模なWindowsアプリであれば、C#とAccessの組み合わせは比較的簡単に導入できます。まずは接続確認とSELECT処理から始め、INSERT、UPDATE、DELETEの順に実装すると、CRUD処理の流れを理解しやすくなります。