티스토리 뷰
// Models/Author.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; // Data Annotations도 함께 사용 가능
namespace CoreMVC.Models
{
public class Author
{
public int Id { get; set; } // 기본 키
[Required]
[StringLength(100)]
public string Name { get; set; }
// 탐색 속성 (Navigation Property)
// 이 저자가 쓴 도서들의 컬렉션 (일대다 관계의 '다' 부분)
public ICollection<Book> Books { get; set; } = new List<Book>();
}
}
// Models/Book.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; // [ForeignKey]를 위해 필요
namespace CoreMVC.Models
{
public class Book
{
public int Id { get; set; } // 기본 키
[Required]
[StringLength(200)]
public string Title { get; set; }
// 외래 키 속성 (저자 ID)
public int AuthorId { get; set; }
// 탐색 속성 (Navigation Property)
// 이 도서의 저자 (일대다 관계의 '일' 부분)
[ForeignKey("AuthorId")] // 외래 키를 명시적으로 지정 (선택 사항, 컨벤션이 유추 가능)
public Author Author { get; set; }
// 탐색 속성 (다대다 관계를 위한 조인 엔티티 컬렉션)
public ICollection<BookTag> BookTags { get; set; } = new List<BookTag>();
}
}
// Models/Tag.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace CoreMVC.Models
{
public class Tag
{
public int Id { get; set; } // 기본 키
[Required]
[StringLength(50)]
public string Name { get; set; }
// 탐색 속성 (다대다 관계를 위한 조인 엔티티 컬렉션)
public ICollection<BookTag> BookTags { get; set; } = new List<BookTag>();
}
}
// Models/BookTag.cs (다대다 관계를 위한 조인 엔티티)
namespace CoreMVC.Models
{
// 이 클래스 자체는 기본 키가 없습니다. OnModelCreating에서 복합 키를 정의합니다.
public class BookTag
{
// 외래 키 속성 (Book의 ID)
public int BookId { get; set; }
// 탐색 속성 (Book)
public Book Book { get; set; }
// 외래 키 속성 (Tag의 ID)
public int TagId { get; set; }
// 탐색 속성 (Tag)
public Tag Tag { get; set; }
}
}
// Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore;
using CoreMVC.Models; // 정의한 모델 클래스들을 참조합니다.
namespace CoreMVC.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
// 각 엔티티에 대한 DbSet을 정의합니다.
public DbSet<Author> Authors { get; set; }
public DbSet<Book> Books { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<BookTag> BookTags { get; set; } // 다대다 관계의 조인 엔티티
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); // 기본 컨벤션 적용
// --- 일대다 (Author <-> Book) 관계 구성 ---
// Author는 여러 Book을 가질 수 있고 (HasMany), Book은 하나의 Author를 가집니다 (WithOne).
modelBuilder.Entity<Author>()
.HasMany(a => a.Books) // Author는 Books 컬렉션을 가짐
.WithOne(b => b.Author) // Book은 하나의 Author를 가짐
.HasForeignKey(b => b.AuthorId) // Book의 AuthorId가 외래 키임을 명시
.OnDelete(DeleteBehavior.Cascade); // Author 삭제 시 관련 Book도 함께 삭제 (기본값)
// .OnDelete(DeleteBehavior.Restrict); // 삭제 제한 (SQL Server 기본값)
// .OnDelete(DeleteBehavior.SetNull); // 외래 키를 NULL로 설정 (AuthorId가 Nullable이어야 함)
// --- 다대다 (Book <-> Tag) 관계 구성 (조인 엔티티 BookTag를 통해) ---
// 1. 조인 엔티티 BookTag의 복합 기본 키 설정
modelBuilder.Entity<BookTag>()
.HasKey(bt => new { bt.BookId, bt.TagId }); // BookId와 TagId의 조합을 복합 키로 지정
// 2. BookTag <-> Book 관계 설정
modelBuilder.Entity<BookTag>()
.HasOne(bt => bt.Book) // BookTag는 하나의 Book을 가짐
.WithMany(b => b.BookTags) // Book은 여러 BookTag를 가질 수 있음
.HasForeignKey(bt => bt.BookId); // BookTag의 BookId가 외래 키임을 명시
// 3. BookTag <-> Tag 관계 설정
modelBuilder.Entity<BookTag>()
.HasOne(bt => bt.Tag) // BookTag는 하나의 Tag를 가짐
.WithMany(t => t.BookTags) // Tag는 여러 BookTag를 가질 수 있음
.HasForeignKey(bt => bt.TagId); // BookTag의 TagId가 외래 키임을 명시
// --- 특정 컬럼에 데이터베이스 특정 타입 지정 ---
// (선택 사항) 예를 들어, Book의 Title 컬럼을 특정 NVARCHAR 타입으로 제한 (기본값은 Max Length)
modelBuilder.Entity<Book>()
.Property(b => b.Title)
.HasColumnType("nvarchar(200)"); // SQL Server의 NVARCHAR(200)으로 정확히 매핑
// --- 인덱스 생성 ---
// (선택 사항) 예를 들어, Author의 Name에 인덱스를 생성하여 검색 성능 향상
modelBuilder.Entity<Author>()
.HasIndex(a => a.Name)
.IsUnique(); // Name이 고유해야 한다면 IsUnique() 추가 (UNIQUE INDEX)
// --- 초기 데이터 시딩 (선택 사항) ---
modelBuilder.Entity<Author>().HasData(
new Author { Id = 1, Name = "제인 오스틴" },
new Author { Id = 2, Name = "조지 오웰" }
);
modelBuilder.Entity<Book>().HasData(
new Book { Id = 1, Title = "오만과 편견", AuthorId = 1 },
new Book { Id = 2, Title = "1984", AuthorId = 2 },
new Book { Id = 3, Title = "동물 농장", AuthorId = 2 }
);
modelBuilder.Entity<Tag>().HasData(
new Tag { Id = 1, Name = "클래식" },
new Tag { Id = 2, Name = "소설" },
new Tag { Id = 3, Name = "디스토피아" },
new Tag { Id = 4, Name = "풍자" }
);
modelBuilder.Entity<BookTag>().HasData(
new BookTag { BookId = 1, TagId = 1 }, // 오만과 편견 -> 클래식
new BookTag { BookId = 1, TagId = 2 }, // 오만과 편견 -> 소설
new BookTag { BookId = 2, TagId = 2 }, // 1984 -> 소설
new BookTag { BookId = 2, TagId = 3 }, // 1984 -> 디스토피아
new BookTag { BookId = 3, TagId = 2 }, // 동물 농장 -> 소설
new BookTag { BookId = 3, TagId = 3 }, // 동물 농장 -> 디스토피아
new BookTag { BookId = 3, TagId = 4 } // 동물 농장 -> 풍자
);
}
}
}
코드 설명:
1. 일대다 관계 (Author <-> Book)
- modelBuilder.Entity<Author>() .HasMany(a => a.Books) .WithOne(b => b.Author) .HasForeignKey(b => b.AuthorId) .OnDelete(DeleteBehavior.Cascade);
- Author 엔티티는 Books 컬렉션(HasMany(a => a.Books))을 가집니다.
- 각 Book 엔티티는 하나의 Author(WithOne(b => b.Author))를 가집니다.
- Book 테이블의 AuthorId 속성이 외래 키(HasForeignKey(b => b.AuthorId))임을 명시합니다.
- OnDelete(DeleteBehavior.Cascade)는 저자가 삭제될 때 해당 저자의 모든 도서도 함께 삭제되도록 설정합니다 (데이터 무결성 유지). 다른 옵션으로는 Restrict, SetNull 등이 있습니다.
2. 다대다 관계 (Book <-> Tag) - 조인 엔티티 BookTag를 통해
다대다 관계는 BookTag라는 중간 조인(Join) 엔티티를 사용하여 구성합니다.
- modelBuilder.Entity<BookTag>().HasKey(bt => new { bt.BookId, bt.TagId });
- BookTag 엔티티는 BookId와 TagId를 조합한 **복합 기본 키(Composite Primary Key)**를 가짐을 정의합니다. 이는 BookId와 TagId의 모든 고유한 조합이 BookTag 테이블의 고유한 행이 됨을 의미합니다. 데이터 어노테이션으로는 복합 키를 정의할 수 없습니다.
- modelBuilder.Entity<BookTag>() .HasOne(bt => bt.Book) .WithMany(b => b.BookTags) .HasForeignKey(bt => bt.BookId);
- BookTag 엔티티가 Book 엔티티와 일대다 관계를 가짐을 정의합니다. BookTag는 하나의 Book을 참조하고, Book은 여러 BookTag를 가집니다. BookId가 외래 키입니다.
- modelBuilder.Entity<BookTag>() .HasOne(bt => bt.Tag) .WithMany(t => t.BookTags) .HasForeignKey(bt => bt.TagId);
- BookTag 엔티티가 Tag 엔티티와 일대다 관계를 가짐을 정의합니다. BookTag는 하나의 Tag를 참조하고, Tag는 여러 BookTag를 가집니다. TagId가 외래 키입니다.
3. 기타 고급 매핑 예시
- HasColumnType("nvarchar(200)"): Book의 Title 속성이 데이터베이스에서 정확히 NVARCHAR(200) 타입으로 매핑되도록 합니다. EF Core는 기본적으로 문자열 속성을 NVARCHAR(MAX) 또는 NVARCHAR(길이)로 유추하지만, 정확한 타입을 명시하는 데 유용합니다.
- HasIndex(a => a.Name).IsUnique(): Author 엔티티의 Name 속성에 데이터베이스 인덱스를 생성하도록 지시합니다. IsUnique()를 추가하면 고유 인덱스가 되어, Name 컬럼에 중복된 값이 들어갈 수 없게 됩니다.
이 예제 코드를 통해 OnModelCreating과 Fluent API를 사용하여 데이터 어노테이션만으로는 정의하기 어려운 복잡한 데이터베이스 관계와 스키마 구성을 어떻게 할 수 있는지 이해하는 데 도움이 되기를 바랍니다.
간단하게 할 수 있는 방법은?
EF Core는 기본적으로 **컨벤션(Conventions)**을 통해 많은 관계를 자동으로 유추합니다.
- 단순한 일대다 관계:
- 예를 들어, Author와 Book 모델에 AuthorId 외래 키와 탐색 속성만 잘 정의되어 있다면, OnModelCreating에서 명시적으로 HasMany().WithOne().HasForeignKey()를 호출하지 않아도 EF Core가 대부분의 경우 관계를 올바르게 유추합니다.
- 예시:
// Author.cs public class Author { public int Id { get; set; } public string Name { get; set; } public ICollection<Book> Books { get; set; } } // Book.cs public class Book { public int Id { get; set; } public string Title { get; set; } public int AuthorId { get; set; } public Author Author { get; set; } }
- 다대다 관계의 단순화 (EF Core 5.0 이상):
- EF Core 5.0부터는 조인 엔티티를 명시적으로 만들지 않고도 두 엔티티 간의 다대다 관계를 직접 구성할 수 있는 기능이 추가되었습니다. 이 경우 EF Core가 내부적으로 조인 테이블을 생성합니다.
- 예시 (EF Core 5.0+):
// Book.cs public class Book { public int Id { get; set; } public string Title { get; set; } public ICollection<Tag> Tags { get; set; } } // Tag.cs public class Tag { public int Id { get; set; } public string Name { get; set; } public ICollection<Book> Books { get; set; } } // AppDbContext.cs protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Book>() .HasMany(b => b.Tags) .WithMany(t => t.Books); }
다대다 관계와 조인 테이블: 추가 컬럼의 의미
다대다 관계와 조인 테이블: 추가 컬럼의 의미간단하게 설명하자면, "조인 테이블에 추가적인 컬럼(예: CreatedDate)이 필요하다면 여전히 명시적인 조인 엔티티를 사용해야 합니다"라는 말은 이런
jangjeonghun.tistory.com
EF Core 저장 프로시저 결과 매핑 전략
저장 프로시저 결과 매핑 전략키 없는 엔티티 타입 (Keyless Entity Type)으로 매핑 (권장):저장 프로시저의 결과가 BookTag 엔티티의 컬럼과 완전히 일치하지 않거나, 더 많은 조인된 정보(예: Book.Title, T
jangjeonghun.tistory.com
728x90
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- In App Purchase
- java.sql
- java-개발 환경 설정하기
- 진수 변환
- 스프링 시큐리티(spring security)-http basic 인증
- 제품 등록
- React
- system.io
- 인텔리제이(intellij)
- await
- 스프링 시큐리티(spring security)
- 메이븐(maven)
- jstl(java standard tag library)
- 람다식(lambda expression)
- REST API
- 문자 자르기
- 스프링 프레임워크(spring framewordk)
- java web-mvc
- 표현 언어(expression language)
- MainActor
- jstl(java standard tag library)-core
- jsp 오픈 소스
- 스프링 프레임워크(spring framework)
- docker
- 특정 문자를 기준으로 자르기
- java 키워드 정리
- error-java
- nl2br
- System.Diagnostics
- .submit()
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
글 보관함