2024. 1. 30. 15:30

몇 가지 관리 이슈 때문에 저는 FK(ForeingKey)를 잘 활용을 하지 않았는데...

원래는 적극적으로 FK를 사용하는 것이 좋습니다.

 

일단 검색 속도가 빨라지는 큰 장점이 있기 때문입니다.

그리고 테이블 간 관계성이 명확해집니다.

 

 

0. 테스트 준비하기 

이 포스트팅에 사용하는 모델 정보는 다음과 같습니다.

(참고 : dang-gun/EntityFrameworkSample/ForeignKeyTest/ModelsDB )

 

ForeignKeyTest1_Blog.cs

namespace ModelsDB;

/// <summary>
/// 테스트용 테이블
/// </summary>
public class ForeignKeyTest1_Blog
{
    /// <summary>
    /// 고유키
    /// </summary>
    [Key]
    public long idTest1Blog { get; set; }

    /// <summary>
    /// 블로그 이름
    /// </summary>
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// 외래키에 연결된 리스트
    /// </summary>
    [ForeignKey("idTest1Blog")]
    public ICollection<ForeignKeyTest1_Post> Posts { get; set; } = new List<ForeignKeyTest1_Post>();
}

 

 

ForeignKeyTest1_Post.cs

namespace ModelsDB;

/// <summary>
/// 테스트용 테이블 - 많은 데이터용
/// </summary>
public class ForeignKeyTest1_Post
{
    /// <summary>
    /// 고유키
    /// </summary>
    [Key]
    public long idTest1Post { get; set; }

    /// <summary>
    /// 숫자형
    /// </summary>
    public int Int { get; set; }
    
    /// <summary>
    /// 문자형
    /// </summary>
    public string Str { get; set; } = string.Empty;

    /// <summary>
    /// 날짜형
    /// </summary>
    public DateTime Date { get; set; }

    /// <summary>
    /// 연결된 외래키
    /// </summary>
    [ForeignKey("idTest1Blog")]
    public long idTest1Blog { get; set; } = 0;
    /// <summary>
    /// 외래키에 연결된 대상
    /// </summary>
    public ForeignKeyTest1_Blog? Blog { get; set; }
}

 

기준이 되는 부모 인덱스의 이름은 'idTest1Blog'입니다.

자식 테이블은 FK 연결 여부와 상관없이 연결된 부모의 인덱스를 가지고 있어야 합니다.

 

 

1. Entity Framework에서 FK(ForeingKey) 연결하기 

모델로 관리하는 Entity Framework(이하 EF)의 특성상 모델의 요소로 선언해야 합니다.

 

FK를 연결하려면 "ForeignKey"속성을 이용하여 연결할 인덱스를 지정합니다.

[ForeignKey("[연결할 인덱스 이름]")]

 

 

부모 테이블에 있는 인덱스가 기준입니다.

 

부모 -> 자식으로 연결하는 FK 한 개

자식 -> 부모로 연결하는 FK 한 개

이렇게 2개가 선언되는 모델입니다.

 

필요에 따라 어느 한 방향만 연결해도 상관없습니다.

 

 

1-1. 자식 FK 연결하기 

부모 -> 자식으로 연결합니다.

자식은 여러 개일 가능성이 있으므로 컬렉션(Collection)과 같은 집합으로 선언해야 합니다.

(참고 : github -  dang-gun/EntityFrameworkSample/ForeignKeyTest/ModelsDB/ForeignKeyTest1_Blog.cs )

 

예 > 

//ICollection으로 선언
[ForeignKey("idTest1Blog")]
public ICollection<ForeignKeyTest1_Post> Posts { get; set; } = new List<ForeignKeyTest1_Post>();

 

자식이 하나뿐이어도 똑같이 선언해야 합니다.

 

 

1-2. 부모 FK 연결하기 

자식 -> 부모로 연결합니다.

 

부모는 하나뿐이므로 부모 개체로 선언하면 됩니다.

단지 부모와 연결될 인덱스를 따로 저장하는 것이 좋습니다.

없어도 EF에서 자동으로 생성하여 연결해 주긴 하지만 관리 측면에서는 있는 것이 좋습니다.

 

예>

    /// <summary>
    /// 연결된 외래키
    /// </summary>
    [ForeignKey("idTest1Blog")]
    public long idTest1Blog { get; set; } = 0;
    /// <summary>
    /// 외래키에 연결된 대상
    /// </summary>
    public ForeignKeyTest1_Blog? Blog { get; set; }

 

 

 

2. 조회하기 

부모/자식을 조회하면 FK를 통해 연결된 자식/부모를 조회할 수 있습니다.

 

2-1. 조회하기 

모델에서 자식을 리스트로 관리하므로 리스트에 접근하듯이 접근하면 됩니다.

using (ModelsDbContext db1 = new ModelsDbContext())
{
	//부모 1개 추출
    ForeignKeyTest1_Blog iq1Blog = db1.ForeignKeyTest1_Blog.First();
    //소속된 자식 추출
    List<ForeignKeyTest1_Post> list1Post = iq1Blog.Posts.ToList();


    //(Include)부모 1개 추출
    ForeignKeyTest1_Blog iq1Blog2 = db1.ForeignKeyTest1_Blog.Include(x => x.Posts).First();
    //소속된 자식 추출
    List<ForeignKeyTest1_Post> list1Post2 = iq1Blog2.Posts.ToList();
}

 

 

하지만 'Include'없이는 자식이 검색되지 않는 것을 알 수 있습니다.

 

 

2-2. 'Include' 사용 

부모를 조회하자마자 만들자마자 자식을 조회한다면 성능에 큰 문제가 있을 것입니다.

그래서 필요할 때 자식을 로드할 수 있도록 'Include'함수가 지원됩니다.

 

자식을 조회할 필요가 있을 때 'Include'함수를 호출하여 자식 리스트를 불러오게 됩니다.

'Include'가 실행되는 시점에 딜레이가 생긴다.

 

 

2-3. 'Include'를 생략할 수 있는 경우 

'Include'를 하지 않아도 조회가 되는 경우도 있습니다.

모든 조건은 'DbContext' 기준입니다.

 

1) 해당 항목이 'Include'된 경우

2) 항목에 대한 조회가 이미 된 경우

    단, 이 경우 조회된 자식만 포함됩니다.

 

'자식이 먼저 조회된 경우'의 결과를 보면 먼저 조회된 자식이 1개뿐이라 소속된 자식이 1개로 나옵니다.

하지만 'Test1Blog 1'의 자식은 19개가 맞습니다.

 

이미 포함된 항목을 다시 'Include'한다면 성능 저하가 생깁니다.

( 'Include' 할 때마다 관련 엔티티를 다시 로드한다.)

적절한 타이밍에 'Include'를 해주고 반복해서 사용하는 것이 좋습니다.

 

 

테스트 코드

using (ModelsDbContext db1 = new ModelsDbContext())
{
    //(Include)부모 1개 추출
    ForeignKeyTest1_Blog iq1Blog2 = db1.ForeignKeyTest1_Blog.Include(x => x.Posts).First();
    //소속된 자식 추출
    List<ForeignKeyTest1_Post> list1Post2 = iq1Blog2.Posts.ToList();


    //부모 1개 추출
    ForeignKeyTest1_Blog iq1Blog = db1.ForeignKeyTest1_Blog.First();
    //소속된 자식 추출
    List<ForeignKeyTest1_Post> list1Post = iq1Blog.Posts.ToList();
}

using (ModelsDbContext db2 = new ModelsDbContext())
{
    ForeignKeyTest1_Post find1Post
        = db2.ForeignKeyTest1_Post
            .Where(w => w.idTest1Blog == 1)
            .First();

    //부모 1개 추출
    ForeignKeyTest1_Blog iq1Blog = db2.ForeignKeyTest1_Blog.First();
    //소속된 자식 추출
    List<ForeignKeyTest1_Post> list1Post = iq1Blog.Posts.ToList();
}

 

 

마무리

테스트 프로젝트 : github - dang-gun/EntityFrameworkSample/ForeignKeyTest/

테스트 프로그램 : 1, 2번 항목입니다.

 

 

FK의 최대 단점은 운영 중에 빠르게 수동으로 데이터를 조작하기가 힘들다는 점입니다.

 

실무에서는 일단 빠르게 데이터로 땜빵(지우거나 변경하거나)해야 하는데 FK로 묶여있으면 연결된 모든 테이블을 수정/삭제를 하면서 역으로 올라와야 하는 경우 빠른 대응이 힘듭니다.

그래서 개발할 때만 FK를 연결하고 서비스할 때는 FK를 제거하는 경우도 있습니다.

 

하지만 대부분의 경우 운영 이슈보다 속도 이슈가 더 큰 문제이므로 FK를 연결하는 것이 좋습니다.