2019. 12. 4. 15:30

코드를 먼저 작성하고 이것을 기반으로 DB를 수정하는 것이 코드 퍼스트(Code First)입니다.

코드 퍼스트가 왜 필요하고 어떻게 사용하는지 알아봅시다.



1. 왜 코드 퍼스트가 필요한가?

전통적인 프로젝트-DB 관계는 DBA가 DB를 만들고 거기에 맞춰 프로그램을 만드는 형태였습니다.

그런데 장비의 가격은 낮아지고 사양은 높아지면서 점점 DB의 성능 이슈를 돈으로 때워도 큰 부담이 안 되는 시대가오게 됩니다!

그러니 프로그래머들이 임시로 DBA를 겸해서 작업하다가 성능 이슈가 발생하는 부분만(혹은 프로젝트가 시작 할 때나 끝날 때쯤) DBA가 붙어서 최적화시키는 프로세스가 정착되었습니다.


DB에 맞춰 프로그램을 만들게 되면 문제가 DB가 수정되면 해당 사항을 프로젝트에서 찾아서 한땀한땀 수정해야 한다는 것입니다.

그래서 나온 것이 ORM(Object-Relational Mapping)개념 입니다.

DB를 객체화해서 관리하겠다는 개념이죠.


DB가 객체화가 된다면 프로그래머 입장에서는 대상 DB가 뭐인지 신경 쓸 필요가 없습니다.

DB는 모델에 맞춰서 작성하면 되므로 MSSQL이든 MYSQL이든 오라클이든 이 모델에만 맞으면 동작하게 됩니다.

(물론 이걸 해내는 건 누군가가 만든 드라이버입니다 ㅎㅎㅎㅎ)


그러다가.....

'매번 DB에 맞춰 프로그램을 만드는 게 아니라 프로그램에 맞춰 DB를 만들면? 안되나?'

라는 생각이 들게 되죠.


이런 발상에서 나온 개념이 코드 퍼스트(Code First)입니다.



C#에서는 엔트리 프레임웍(EF, Entity Framework)이 'ORM' 역할을 합니다.

이제 프로젝트에서 모델을 만들고 이 모델을 기반으로 DB를 수정합니다.


그렇다는 것은 코드 퍼스트로 만들게 되면 어떤 DB엔진이던 상관없이  DB신경안쓰고 만들 수 있다는 것입니다!


2. 프로젝트 준비

프로젝트는 생성 옵션은

닷넷 코어 2.2

웹 응용프로그램

WebAPI

입니다.



누겟에서

Microsoft.EntityFrameworkCore.Tools

를 찾아 설치합니다.




DB엔진에 따라 추가로 설치해야 할 프로바인더(provider) 다릅니다.

MSSQL : Microsoft.EntityFrameworkCore.SqlServer

MySql : MySql.Data.EntityFrameworkCore

Oracle : Oracle.EntityFrameworkCore

SQLite : Microsoft.EntityFrameworkCore.Sqlite




3. DB 커낵션 스트링(Connection String) 만들기

이 프로젝트에서 저는 MSSQL을 사용합니다.

하지만 위에서도 설명했다시피 어떤 DB엔진을 사용해도 상관없습니다.

단지 각 DB엔진의 종류와 버전에 맞는 EF를 찾아 설치해야 합니다.


'appsettings.json'파일을 열어 'ConnectionString'를 추가하여 커낵션 스트링을 추가해줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
{
  "Logging": {
    "LogLevel": {
      "Default""Warning"
    }
  },
  "ConnectionString": {
    "MSSQL_EFCoreCodeFirstSample""Server=[주소];DataBase=[데이터 베이스];UId=[아이디];pwd=[비밀번호]"
  },
  "AllowedHosts""*"
}
 
cs



커낵션 스트링을 DB엔진 별로 약간씩 차이가 있습니다.

사용하는 DB엔진에 맞춰 수정하도록 합시다.



4. 모델(Model)과 컨텍스트
컨텍스트는 DB역할

모델은 DB의 테이블역할을

하게 됩니다.


4-1. 모델 만들기
'ModelDB'폴더를 생성합니다.

'TestUser' 클래스를 추가합니다.


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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/// <summary>
/// 유저 가입 타입
/// </summary>
public enum UserJoinType
{
    None = 0,
 
    Normal,
    VIP,
    VVIP,
}
 
/// <summary>
/// 테스트용 유저 정보 모델
/// </summary>
public class TestUser
{
    /// <summary>
    /// 고유키
    /// </summary>
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long TestUserID { get; set; }
 
    /// <summary>
    /// 사인인에 사용하는 이메일
    /// </summary>
    public string Email { get; set; }
    /// <summary>
    /// 비밀번호
    /// </summary>
    public string Password { get; set; }
 
    /// <summary>
    /// 가입 날짜
    /// </summary>
    public DateTime JoinDate { get; set; }
    /// <summary>
    /// 가입 형태
    /// </summary>
    public UserJoinType JoinType { get; set; }
 
    /// <summary>
    /// 보유 금액
    /// </summary>
    public double Money { get; set; }
}
cs



4-2. 컨택스트 만들기
컨텍스트는 EF를 사용할 때 DB를 컨트롤하기 위한 객체입니다.

간단하게 말하자면 DB와 연결하여 동작하는 객체라고 보시면 됩니다.


'ModelDB'폴더에 'EFCoreCodeFirstSampleContext'를 생성하고 아래와 같이 작성합니다.


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
32
33
34
35
36
public class EFCoreCodeFirstSampleContext : DbContext
{
    public EFCoreCodeFirstSampleContext(DbContextOptions options)
        : base(options)
    {
    }
 
    public DbSet<TestUser> TestUser { get; set; }
 
    /// <summary>
    /// 테이블이 생성될때 동작
    /// </summary>
    /// <param name="modelBuilder"></param>
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //'TestUser'에 기본 정보 추가
        modelBuilder.Entity<TestUser>().HasData(new TestUser 
        { 
            idTestUser = 1
            , Email = "test01@test.com"
            , Password = "1111"
            , JoinType = UserJoinType.VVIP
            , JoinDate = new DateTime(20191010)
            , Money = 10.1
        }
        , new TestUser 
        { 
            idTestUser = 2
            , Email = "test02@test.com"
            , Password = "1111"
            , JoinType = UserJoinType.Normal
            , JoinDate = new DateTime(20191210)
            , Money = 1000.22
        });
    }
}
cs



4-3. DB 연결
DB에 연결하려면 위에서 만든 커낵션 스트링을 불러와 연결해야 합니다.

'Startup.cs'를 열어 'ConfigureServices' 안에 'services.AddDbContext'를 추가해줍니다.


1
2
3
4
5
6
7
8
9
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
 
    services.AddDbContext<EFCoreCodeFirstSampleContext>(opts =>
        //opts.UseSqlServer(Configuration["ConnectionString:EmployeeDB"]
        opts.UseSqlServer(Configuration["ConnectionString:MSSQL_EFCoreCodeFirstSample"]
        ));
}
cs


5. 마이그레이션(Migration) 만들기

코드 퍼스트의 동작 원리는 버전 관리시스템과 유사합니다.


마이그레이션(Migration) 정보를 생성하여 버전을 관리하고

이것을 DB에 업데이트하여 프로젝트의 모델 정보를 DB에 업데이트합니다.


5-1. 첫 마이그레이션 만들기

지금은 DB만 생성이 되어 있고 내용은 없는 상태입니다.




마이그래이션을 생성하는 명령어는 다음과 같습니다.

Add-Migration [마이그레이션 이름]



패키지 관리자를 열고 다음 명령어를 실행합니다.


1
Add-Migration DB생성
cs




마이그레이션 이름은 클래스 이름 규칙을 따릅니다.

띄어쓰기 같은 것이 안 된다는 것이죠.



'Migrations'폴더가 생성되고 마이그레이션 정보가 들어 있는 파일이 생성된 것을 확인 할 수 있습니다.




5-2. DB에 반영하기
아직 DB에는 내용이 반영되지 않았습니다.


업데이트 명령어를 통해 DB에 적용할 마이그레이션 버전을 선택해야 합니다.

update-database [마이그레이션 이름]



다음 명령을 통해 아까 생성한 마이그레이션을 선택해 봅시다.


1
update-database DB생성
cs



에러가 없다면 아래와 같이 'Done.'가 표시됩니다.




이제 DB를 보면 업데이트 된 것을 확인 할 수 있습니다.




5-3. 수정하기

모델을 수정하면 어떻게 동작하게 되는지 확인해 봅시다.


'TestUser'에 'Message'를 추가합니다.


1
2
3
4
/// <summary>
/// 메시지
/// </summary>
public string Message { get; set; }
cs



아래 명령을 실행합니다.


1
Add-Migration TestUser_에_Message_추가
cs



마이그레이션 정보가 생성된 것을 확인합니다.




다음 명령을 사용하여 DB에 적용해 봅시다.


1
update-database TestUser_에_Message_추가
cs



 DB 테이블이 변경된 것을 확인할 수 있습니다.

자동으로 생성된 '__EFMigrationsHistory'테이블을 확인하면 마이그레이션 정보도 남아 있습니다.




5-4. 컬럼 옵션 넣기

컬럼의 글자 수 제한이라던가 키와 관련된 내용은 필터를 추가해서 처리할 수 있습니다.

자세한 내용은 아래 링크를 확인해 주세요.

참고 : Microsoft docs - Entity Framework Core/모델 만들기 개요


'TestUser'모델의 'Message'는 지워줍니다.

'TestUserInfo'모델을 만들고 아래와 같이 작성합니다.


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
32
/// <summary>
/// 유저 상세 정보
/// </summary>
public class TestUserInfo
{
    /// <summary>
    /// 고유키
    /// </summary>
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long idTestUserInfo { get; set; }
 
    /// <summary>
    /// TestUser FK
    /// </summary>
    [ForeignKey("idTestUserForeignKey")]
    public TestUser idTestUser { get; set; }
 
    /// <summary>
    /// 래벨.
    /// 필수값
    /// </summary>
    [Required]
    public int Lv { get; set; }
 
    /// <summary>
    /// 닉네임
    /// 10자리
    /// </summary>
    [MaxLength(10)]
    public string NickName { get; set; }
}
cs



'EFCoreCodeFirstSampleContext'에 방금 추가한 모델을 선언해 줍니다.


1
2
public DbSet<TestUser> TestUser { get; set; }
public DbSet<TestUserInfo> TestUserInfo { get; set; }
cs



마이그레이션을 생성해 봅시다.

이번에는 컬럼이 제거되어서 데이터가 손실될 수 있다는 경고가 표시됩니다.




생성한 마이그레이션을 업데이트해 봅시다.




필터가 컬럼 옵션이 되어 잘 작용 된 것을 볼 수 있습니다.


5-5. 업데이트 제외하기

특정 테이블을 수동으로 관리하려면 제외시켜 놔야 합니다.


'TestIgnore.cs'를 생성하고 아래와 같이 작성합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// <summary>
/// 제외 테스트용 모델
/// </summary>
public class TestIgnore
{
    /// <summary>
    /// 고유키
    /// </summary>
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long idTestIgnore { get; set; }
 
        
    [Required]
    public int Count { get; set; }
}
cs



'EFCoreCodeFirstSampleContext'에 방금 추가한 모델을 선언해 줍니다.


1
2
3
public DbSet<TestUser> TestUser { get; set; }
public DbSet<TestUserInfo> TestUserInfo { get; set; }
public DbSet<TestIgnore> TestIgnore { get; set; }
cs



방금 추가한 모델을 제외하기 위해 'OnModelCreating'에 다음 코드를 추가합니다.


1
2
//제외
modelBuilder.Ignore<TestIgnore>();
cs



이제 마이그레이션을 생성하고 업데이트를 해보면 'TestIgnore'테이블이 생성되지 않을 걸 알 수 있습니다.



6. 마이그레이션 초기화
https://weblog.west-wind.com/posts/2016/jan/13/resetting-entity-framework-migrations-to-a-clean-slate




마무리

완성된 샘플 : Github dang-gun - EntityFrameworkSample/CoreCodeFirst/


보시면 알 수 있는 것이.....

생성된 테이블들이 최적화 돼있다는 보장은 없습니다 ㅎㅎㅎㅎㅎ


그럴 땐 제외시켜놓고 수동으로 관리하면 됩니다.

뭐....가능하면 수동으로 관리해야 할게 없는 게 맞긴 하겠지만 말이죠 ㅎㅎㅎㅎ

댓글 작성

이름
패스워드
홈페이지
비밀글