0Pricing
C# Academy · 강의

관계: 일대다와 다대다

탐색 속성과 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 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. DbContext와 DbSet 기초
  2. 마이그레이션과 스키마 관리
  3. EF Core를 사용한 CRUD 작업
  4. 관계: 일대다와 다대다
← C# Academy(으)로 돌아가기