リレーションシップ:一対多と多対多
ナビゲーションプロパティとFluent APIを使って、一対多および多対多のリレーションシップをモデル化し、クエリを実行します。
「リレーションシップ:一対多と多対多」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
EF Core のリレーションシップ
EF Core は、エンティティ間のリレーションシップを一対一、一対多、多対多の 3 種類に分類します。それぞれはナビゲーションプロパティで表現し、必要に応じて Fluent API で構成します。
一対多: エンティティの設定
1 つの Blog には複数の Post があります。依存エンティティ(Post)が外部キーを保持します。両側のナビゲーションプロパティにより、どちらの方向にも辿れます。
public class Blog
{
public int Id { get; set; }
public string Name { get; set; } = "";
public ICollection<Post> Posts { get; set; } = new List<Post>();
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; } = "";
public int BlogId { get; set; } // FK
public Blog Blog { get; set; } = null!; // nav property
}Fluent API: 一対多
EF Core は規約によってリレーションシップを推論しますが、Fluent API を使って明示的に設定することで、意図を明確にし、カスケード削除の動作を指定できます。
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<Blog>()
.HasMany(b => b.Posts)
.WithOne(p => p.Blog)
.HasForeignKey(p => p.BlogId)
.OnDelete(DeleteBehavior.Cascade);
}Include によるクエリ(イーガーローディング)
Include を使用すると、関連データを同じクエリで読み込めます。ナビゲーションのグラフをさらに深く辿るには、ThenInclude を使用します。
// Load blog with all its posts
var blog = await _db.Blogs
.Include(b => b.Posts)
.FirstOrDefaultAsync(b => b.Id == 1);
// Load posts with their comments too
var blogs = await _db.Blogs
.Include(b => b.Posts)
.ThenInclude(p => p.Comments)
.ToListAsync();結合エンティティなしの多対多
EF Core 5 以降では、明示的な結合エンティティなしで多対多のリレーションシップを構成できます。エンティティ型の名前を使用して、結合テーブルが自動的に作成されます。
public class Student
{
public int Id { get; set; }
public string Name { get; set; } = "";
public ICollection<Course> Courses { get; set; } = new List<Course>();
}
public class Course
{
public int Id { get; set; }
public string Title { get; set; } = "";
public ICollection<Student> Students { get; set; } = new List<Student>();
}
// EF creates StudentCourse join table automatically結合エンティティありの多対多
結合テーブルに追加の列(登録日や成績など)が必要な場合は、複合主キーを持つ明示的な結合エンティティを作成します。
public class Enrollment
{
public int StudentId { get; set; }
public Student Student { get; set; } = null!;
public int CourseId { get; set; }
public Course Course { get; set; } = null!;
public DateTime EnrolledAt { get; set; }
public decimal? Grade { get; set; }
}
// Fluent API config:
mb.Entity<Enrollment>().HasKey(e => new { e.StudentId, e.CourseId });
mb.Entity<Enrollment>().HasOne(e => e.Student).WithMany(s => s.Enrollments);
mb.Entity<Enrollment>().HasOne(e => e.Course).WithMany(c => c.Enrollments);関連データの挿入
SaveChangesAsync を呼び出す前に、関連エンティティを親のコレクションに追加します。EF Core が FK の値を自動的に処理します。
var blog = new Blog { Name = "Dev Notes" };
blog.Posts.Add(new Post { Title = "Hello EF Core" });
blog.Posts.Add(new Post { Title = "Migrations Deep Dive" });
_db.Blogs.Add(blog);
await _db.SaveChangesAsync();
// Inserts Blog + 2 Posts with correct BlogId FK関連データによるフィルタリング
LINQ では、ナビゲーションプロパティを条件にしてフィルタリングできます。EF Core がこれらを自動的に SQL JOIN に変換します。
// Blogs that have at least one published post
var popularBlogs = await _db.Blogs
.Where(b => b.Posts.Any(p => p.IsPublished))
.Select(b => new { b.Name, PostCount = b.Posts.Count(p => p.IsPublished) })
.OrderByDescending(b => b.PostCount)
.ToListAsync();カスケード削除
DeleteBehavior.Cascade を設定すると、プリンシパル(Blog)を削除したときに、すべての依存エンティティ(Posts)も自動的に削除されます。その他のオプションには、SetNull、Restrict、NoAction があります。
// Delete a blog and all its posts
var blog = await _db.Blogs.FindAsync(1);
_db.Blogs.Remove(blog!);
await _db.SaveChangesAsync();
// Cascade: all Posts with BlogId=1 are also deleted値オブジェクトのための所有エンティティ
所有エンティティ型は、値オブジェクト(Address など)に適しています。所有者と同じテーブルに格納され、独立した識別子を持ちません。
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "";
public Address ShippingAddress { get; set; } = new();
}
public class Address
{
public string Street { get; set; } = "";
public string City { get; set; } = "";
public string Country { get; set; } = "";
}
// Fluent API:
mb.Entity<Customer>().OwnsOne(c => c.ShippingAddress);実践例: 明細行を持つ Order
完全な e コマースのパターンです。Order(プリンシパル)が複数の OrderLines(依存エンティティ)を持ち、それぞれが Product を参照します。
var order = await _db.Orders
.Include(o => o.Lines)
.ThenInclude(l => l.Product)
.FirstOrDefaultAsync(o => o.Id == orderId);
if (order is null) return Results.NotFound();
var summary = new
{
OrderId = order.Id,
Total = order.Lines.Sum(l => l.Quantity * l.Product.Price),
Items = order.Lines.Select(l => new
{
l.Product.Name,
l.Quantity,
LineTotal = l.Quantity * l.Product.Price
})
};
return Results.Ok(summary);理解度チェック
明示的な結合エンティティなしで、EF Core が結合テーブルを自動的に作成するのはどのような場合ですか?
まとめ: EF Core のリレーションシップ
重要なポイント:
- 一対多: FK は依存エンティティにあり、両側にナビゲーションがあります。Include を使用して読み込みます
- 多対多(単純な構成): 2 つの ICollection ナビゲーションがあれば、EF Core が結合テーブルを自動作成します
- 多対多(追加データあり): 複合 PK を持つ明示的な結合エンティティを使用します
- カスケード削除によって、プリンシパルの削除時に依存エンティティをどう扱うかを制御します
- 所有エンティティによって、所有者と同じテーブル内の値オブジェクトをモデル化します
よくある質問
「リレーションシップ:一対多と多対多」レッスンは無料ですか?
はい。「リレーションシップ:一対多と多対多」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「リレーションシップ:一対多と多対多」で何を学びますか?
ナビゲーションプロパティとFluent APIを使って、一対多および多対多のリレーションシップをモデル化し、クエリを実行します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「リレーションシップ:一対多と多対多」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- DbContextとDbSetの基礎
- Migrationとスキーマ管理
- EF CoreによるCRUD操作
- リレーションシップ:一対多と多対多