관계: 일대다와 다대다
탐색 속성과 Fluent API로 일대다 및 다대다 관계를 모델링하고 조회합니다.
관계: 일대다와 다대다은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C# Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
EF Core의 관계
EF Core는 엔터티 간의 관계를 일대일, 일대다, 다대다의 세 가지 유형으로 모델링합니다. 각 관계는 탐색 속성으로 표현하며, 필요에 따라 Fluent 방식으로 구성할 수 있습니다.
일대다: 엔터티 설정
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 방식: 일대다
EF Core는 관례에 따라 관계를 추론하지만, 명확성을 높이고 연쇄 삭제 동작을 설정하려면 Fluent 방식으로 관계를 명시할 수 있습니다.
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가 이를 자동으로 데이터베이스의 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)를 삭제할 때 모든 종속 엔터티(Post)가 자동으로 삭제됩니다. 다른 옵션으로는 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)에 적합합니다. 소유자와 동일한 테이블에 저장되며 독립적인 ID가 없습니다.
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);실전: 항목이 포함된 주문
완전한 전자 상거래 패턴입니다. 주문(주 엔터티)에 여러 OrderLines(종속 엔터티)가 연결되고, 각 OrderLine이 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로 로드합니다
- 다대다(단순한 형태): 두 개의 ICollection 탐색 속성을 사용하면 EF Core가 연결 테이블을 자동으로 생성합니다
- 다대다(추가 데이터를 포함하는 형태): 복합 PK를 가진 명시적 연결 엔터티를 사용합니다
- 연쇄 삭제는 주 엔터티가 제거될 때 종속 엔터티에 발생하는 일을 제어합니다
- 소유 엔터티를 사용하면 소유자와 같은 테이블에 값 개체를 모델링할 수 있습니다
자주 묻는 질문
“관계: 일대다와 다대다” 강의는 무료인가요?
네 — “관계: 일대다와 다대다” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“관계: 일대다와 다대다”에서 뭘 배우나요?
탐색 속성과 Fluent API로 일대다 및 다대다 관계를 모델링하고 조회합니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
C# Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“관계: 일대다와 다대다” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.