前回まで、ADO.NET を使って接続、コマンド、データ読み取り、更新、トランザクション、一括投入を扱いました。
ADO.NET は、SQL とデータベース操作を明示的に扱える強力な仕組みです。
一方で、アプリケーションの規模が大きくなると、SQL の組み立て、読み取り結果のモデル変換、変更追跡、リレーションの扱いなどを毎回手で書くのは大変になります。
そこで登場するのが Entity Framework Core です。
オブジェクトとテーブルのずれ
C# のアプリケーションでは、データをクラスとして扱います。
public sealed class Car
{
public int Id { get; set; }
public string Make { get; set; } = "";
public string Color { get; set; } = "";
public string PetName { get; set; } = "";
}
一方、リレーショナルデータベースでは、データをテーブルの行として扱います。
CREATE TABLE Inventory
(
Id INT NOT NULL PRIMARY KEY,
Make NVARCHAR(50) NOT NULL,
Color NVARCHAR(50) NOT NULL,
PetName NVARCHAR(50) NOT NULL
);
アプリケーション側では「Car オブジェクト」、データベース側では「Inventory テーブルの行」です。
この 2 つの世界には、次のようなずれがあります。
- クラスとテーブル
- プロパティと列
- オブジェクト参照と外部キー
- コレクションと 1 対多のリレーション
- 継承とテーブル設計
- null の扱い
- 変更検知の仕組み
このずれを埋める仕組みが ORM です。
ORM とは
ORM は、オブジェクトとリレーショナルデータベースを対応付けるための仕組みです。
Entity Framework Core を使うと、C# のクラスを中心にデータベースを扱えます。
await using AutoLotContext context = new AutoLotContext();
List<Car> cars = await context.Cars
.Where(car => car.Make == "Honda")
.OrderBy(car => car.PetName)
.ToListAsync();
このコードでは SQL を直接書いていません。
しかし、EF Core は LINQ 式を解析し、データベースへ送る SQL を生成します。
概念的には、次のような SQL になります。
SELECT Id, Make, Color, PetName
FROM Inventory
WHERE Make = N'Honda'
ORDER BY PetName;
つまり、EF Core は「C# の式」と「SQL」の橋渡しをしてくれます。
EF Core が担当すること
EF Core は、主に次のような処理を担当します。
モデルとテーブルの対応付け
どのクラスをどのテーブルに対応させるか、どのプロパティをどの列に対応させるかを管理します。
クエリの変換
LINQ で書いた問い合わせを、データベースが理解できる SQL に変換します。
変更追跡
取得したオブジェクトが変更されたかどうかを追跡します。
SaveChanges() を呼ぶと、変更内容に応じて INSERT / UPDATE / DELETE が実行されます。
リレーションの扱い
1 対多、多対多、所有される値オブジェクトなど、テーブル間の関係を C# のオブジェクトとして扱いやすくします。
マイグレーション
モデルの変更からデータベーススキーマの変更履歴を作成し、データベースへ適用できます。
ADO.NET との違い
ADO.NET では、SQL と読み取り処理を明示的に書きます。
await using SqlCommand command = connection.CreateCommand();
command.CommandText = """
SELECT Id, Make, Color, PetName
FROM Inventory
WHERE Make = @make
""";
command.Parameters.AddWithValue("@make", "Honda");
await using SqlDataReader reader = await command.ExecuteReaderAsync();
EF Core では、LINQ とモデルを中心に書けます。
List<Car> cars = await context.Cars
.Where(car => car.Make == "Honda")
.ToListAsync();
どちらが優れている、という話ではありません。
用途が違います。
ADO.NET が向いている場面:
- SQL を細かく制御したい
- 大量投入など低レベルな最適化が必要
- 単純で小さなデータアクセスだけで済む
- ORM の変換を挟みたくない
EF Core が向いている場面:
- ドメインモデルを中心に開発したい
- CRUD 処理が多い
- LINQ で問い合わせを書きたい
- 変更追跡やリレーション管理を任せたい
- マイグレーションでスキーマ変更を管理したい
実務では、EF Core を基本にしつつ、必要な場所だけ ADO.NET や生 SQL を使う設計もよくあります。
最小構成の例
EF Core を使うには、まずパッケージを追加します。
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
モデルです。
public sealed class Car
{
public int Id { get; set; }
public string Make { get; set; } = "";
public string Color { get; set; } = "";
public string PetName { get; set; } = "";
}
データベースとの窓口になるクラスです。
using Microsoft.EntityFrameworkCore;
public sealed class AutoLotContext : DbContext
{
public DbSet<Car> Cars => Set<Car>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
"Server=localhost,1433;Database=AutoLot;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>().ToTable("Inventory");
}
}
問い合わせです。
await using AutoLotContext context = new AutoLotContext();
List<Car> cars = await context.Cars
.OrderBy(car => car.Id)
.ToListAsync();
foreach (Car car in cars)
{
Console.WriteLine($"{car.Id}: {car.Color} {car.Make} ({car.PetName})");
}
この時点で、EF Core は Car と Inventory テーブルの対応を使って SQL を生成します。
接続文字列は設定へ逃がす
サンプルでは分かりやすくするために接続文字列をコードに書きました。
実務では設定から渡します。
public sealed class AutoLotContext : DbContext
{
public AutoLotContext(DbContextOptions<AutoLotContext> options)
: base(options)
{
}
public DbSet<Car> Cars => Set<Car>();
}
ASP.NET Core や Generic Host では、DI に登録できます。
builder.Services.AddDbContext<AutoLotContext>(options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString("AutoLot"));
});
この形にすると、接続文字列やテスト用設定を差し替えやすくなります。
EF Core を使うときの心構え
EF Core は便利ですが、魔法ではありません。
LINQ で書いた処理は、最終的に SQL としてデータベースで実行されます。
次の点を意識すると、トラブルを避けやすくなります。
- 生成される SQL を確認する
- 必要以上に大量データを読み込まない
Includeの使いすぎに注意する- 更新対象の状態を理解する
- トランザクション境界を意識する
- 追跡あり/なしを使い分ける
EF Core は、データベースを知らなくてよくする道具ではありません。
データベースの知識を前提に、C# から扱いやすくする道具です。
まとめ
Entity Framework Core は、C# のオブジェクトとリレーショナルデータベースを対応付けるための ORM です。
LINQ による問い合わせ、変更追跡、リレーション管理、マイグレーションなどを提供します。
ADO.NET より高い抽象度でデータアクセスを書けますが、最終的には SQL が実行されます。
次回は、EF Core の基本部品であるコンテキスト、データ集合、変更追跡を詳しく見ていきます。