ASP.NET Core 3.4 · 3부. 데이터 접근

마이그레이션

형식이 바뀔 때 표도 함께 바꿉니다.

예상 학습 시간 18분 난이도 중급
개념 설명

표가 이미 자료를 담고 있습니다

3.1에서 EnsureCreated() 로 표를 만들었습니다. 없으면 만들고 있으면 그냥 둡니다. 형식이 바뀌어도 표는 바뀌지 않습니다. 처음 배울 때는 지우고 다시 만들면 되지만, 운영 중인 데이터베이스에서는 그럴 수 없습니다.

마이그레이션은 바뀐 만큼만 적용하는 방법입니다. 형식을 고칠 때마다 무엇이 달라졌는지를 코드 파일로 남기고, 데이터베이스에는 아직 적용하지 않은 것만 순서대로 적용합니다.

그 코드 파일은 저장소에 함께 담습니다. 그래야 다른 사람의 기계와 운영 서버가 같은 순서로 같은 것을 적용합니다.

최소 예제

첫 마이그레이션 만들기

명령 도구를 먼저 갖춥니다. 프로젝트에는 Design 패키지가 필요합니다.

터미널
dotnet tool install --global dotnet-ef
dotnet add package Microsoft.EntityFrameworkCore.Design

dotnet ef migrations add 첫판
만들어진 파일
Migrations/ ├─ 20260829151044_첫판.cs ← 바뀐 것을 적은 코드 ├─ 20260829151044_첫판.Designer.cs ← 그때의 모델 정보 └─ TodoContextModelSnapshot.cs ← 지금 모델의 사진

이 단원의 명령은 브라우저가 아니라 여러분의 터미널에서 돌립니다.

앞의 숫자는 만든 시각입니다. 이것이 적용 순서를 정합니다. 여러 사람이 각각 만들어도 시각으로 줄이 세워집니다.

가운데 파일이 실제로 하는 일입니다.

20260829151044_첫판.cs
protected override void Up(MigrationBuilder migrationBuilder) {
    migrationBuilder.CreateTable(
        name: "Todos",
        columns: table => new {
            Id = table.Column<int>(type: "INTEGER", nullable: false)
                .Annotation("Sqlite:Autoincrement", true),
            Title = table.Column<string>(type: "TEXT", nullable: false),
            Done = table.Column<bool>(type: "INTEGER", nullable: false)
        },
        constraints: table => {
            table.PrimaryKey("PK_Todos", x => x.Id);
        });
}

protected override void Down(MigrationBuilder migrationBuilder) {
    migrationBuilder.DropTable(name: "Todos");
}
Up 과 Down
Up 앞으로 나아갈 때 하는 일 Down 되돌릴 때 하는 일 — Up 의 반대입니다

아직 데이터베이스는 바뀌지 않았습니다. 파일만 만들어졌습니다. 적용은 따로 합니다.

상세 사용법

적용하기

적용하는 방법이 둘입니다. 개발 기계에서는 명령으로, 앱 안에서는 Migrate() 로 합니다.

# 터미널에서
dotnet ef database update

// 또는 앱이 뜰 때
await db.Database.MigrateAsync();

적용한 것을 데이터베이스가 기억합니다. __EFMigrationsHistory 라는 표가 그 자리입니다. 첫 적용 때 함께 만들어집니다.

dotnet ef migrations script
CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
    "MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
    "ProductVersion" TEXT NOT NULL
);

BEGIN TRANSACTION;
CREATE TABLE "Todos" (…);

INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20260829151044_첫판', '10.0.11');
COMMIT;

BEGIN TRANSACTION;
ALTER TABLE "Todos" ADD "Priority" INTEGER NOT NULL DEFAULT 0;

INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20260829151108_중요도추가', '10.0.11');
COMMIT;
보이는 것
· 마이그레이션 하나가 트랜잭션 하나입니다 · 기록 표에 넣는 것까지 같은 트랜잭션 안입니다 · 그래서 중간에 실패하면 그 마이그레이션은 통째로 없던 일이 됩니다

운영에는 이 스크립트를 사용하는 편이 안전합니다. 앱이 뜰 때 Migrate() 를 부르면 편하지만, 서버가 여럿이면 동시에 부르게 되고 무엇이 적용될지 미리 볼 수 없습니다. 스크립트를 뽑아 두면 사람이 읽고 승인한 뒤 적용할 수 있습니다.

# 지금 적용된 것부터 마지막까지
dotnet ef migrations script --idempotent -o deploy.sql

# 무엇이 아직 적용되지 않았는지 보기
dotnet ef migrations list
dotnet ef migrations list
20260829151044_첫판
20260829151108_중요도추가 (Pending)
20260829151121_이름바꾸기 (Pending)
읽는 법
(Pending) 이 붙은 것이 아직 적용되지 않은 것입니다.
상세 사용법

무엇을 고쳤느냐에 따라 다릅니다

형식을 고치고 migrations add 를 부르면, EF Core 가 지난번 사진과 비교해 달라진 것을 알아냅니다. 세 가지를 고쳐 보았습니다.

고친 것만들어진 것
속성 추가AddColumn — 이미 있는 줄에는 기본값이 들어갑니다
속성 이름 변경RenameColumn — 값이 그대로 옮겨 갑니다
속성 형식 변경AlterColumn
각각 만들어진 Up
// int Priority 를 더했을 때
migrationBuilder.AddColumn<int>(
    name: "Priority", table: "Todos", type: "INTEGER",
    nullable: false, defaultValue: 0);

// Title 을 Subject 로 바꿨을 때
migrationBuilder.RenameColumn(
    name: "Title", table: "Todos", newName: "Subject");

// Priority 를 int 에서 string 으로 바꿨을 때
migrationBuilder.AlterColumn<string>(
    name: "Priority", table: "Todos", type: "TEXT",
    nullable: false, oldClrType: typeof(int), oldType: "INTEGER");
눈여겨볼 곳
· 추가한 컬럼이 NOT NULL 이면 defaultValue 가 붙습니다 (이미 있는 줄을 채워야 하기 때문입니다) · 이름 변경은 값을 옮깁니다. 지우고 다시 만들지 않습니다

만들어진 파일을 반드시 열어 보십시오. 알아내지 못하는 경우가 있습니다. 이름을 바꾸면서 형식도 함께 바꾸거나, 여러 속성을 한꺼번에 손대면 DropColumnAddColumn 이 나올 수 있습니다. 그러면 그 칸의 자료가 사라집니다. 그때는 손으로 RenameColumn 으로 고쳐 적습니다.

이름을 바꾸는 마이그레이션은 따로 하나로 만드는 편이 안전합니다. 다른 변경과 섞이지 않으면 알아내기 쉽습니다.

버전 배지

적용한 뒤에는 고치지 않습니다

규칙

아직 적용하지 않은 마이그레이션은 dotnet ef migrations remove 로 지우고 다시 만들면 됩니다. 그런데 이미 다른 사람의 기계나 운영에 적용된 것은 고치지 마십시오. 기록 표에는 그 이름이 적용된 것으로 남아 있어, 내용을 바꿔도 다시 적용되지 않습니다.

고쳐야 한다면 새 마이그레이션을 하나 더 만듭니다. 되돌려야 한다면 dotnet ef database update 앞의이름 으로 그 지점까지 Down 을 적용합니다.

실습 문제

직접 해보기

1. 마감일 더하기 난이도 하

DateOnly? Due 를 더하고 마이그레이션을 만들어 적용해보세요. 이미 담긴 줄에는 무엇이 들어갈지 먼저 짐작해보십시오.

물음표가 붙었습니다. 값이 없어도 되는 칸에는 기본값이 필요합니까.
dotnet ef migrations add 마감일추가 dotnet ef database update // 만들어지는 것 migrationBuilder.AddColumn<DateOnly>( name: "Due", table: "Todos", nullable: true); // nullable 이라 defaultValue 가 없습니다. 이미 담긴 줄은 NULL 입니다. // 물음표를 빼면 defaultValue 가 붙고, 모든 줄이 그 값으로 채워집니다.
2. 되돌려 보기 난이도 중

방금 적용한 것을 되돌리고, 마이그레이션 파일도 지우려면 어떤 명령을 어떤 순서로 부를지 적어보세요.

파일을 먼저 지우면 되돌릴 코드가 없어집니다.
# 1. 데이터베이스를 앞의 지점까지 되돌립니다 dotnet ef database update 이름바꾸기 # 2. 그 다음에 파일을 지웁니다 dotnet ef migrations remove # 순서를 바꾸면 remove 가 거절합니다 — 적용된 것은 지울 수 없습니다. # 처음으로 다 되돌리려면 update 0 입니다.
3. 자료가 사라지는 자리 난이도 상

string Titlestring Subject 로 바꾸면서 동시에 int Priority 도 지웠습니다. 만들어진 파일에서 무엇을 확인해야 하는지와, 잘못 나왔다면 어떻게 고칠지 적어보세요.

한 번에 여럿을 손대면 무엇이 무엇으로 바뀐 것인지 알아내기 어렵습니다.
// 확인할 것 : Title 이 RenameColumn 으로 나왔는가. // DropColumn("Title") + AddColumn("Subject") 로 나왔다면 // 이미 담긴 제목이 모두 사라집니다. // 고치는 법 : 손으로 바꿔 적습니다. migrationBuilder.RenameColumn( name: "Title", table: "Todos", newName: "Subject"); migrationBuilder.DropColumn(name: "Priority", table: "Todos"); // Down 도 함께 고쳐야 되돌릴 수 있습니다. // 더 나은 방법 : 이름 변경만 따로 하나 만들고, 지우는 것은 // 그 다음에 따로 만듭니다.
요약
  • 마이그레이션은 바뀐 만큼만 적용해 이미 담긴 자료를 지킵니다.
  • migrations add 는 파일만 만듭니다. 적용은 database update 입니다.
  • 적용한 것은 __EFMigrationsHistory 에 기록되고, 마이그레이션 하나가 트랜잭션 하나입니다.
  • 만들어진 파일을 열어 보십시오. 이름 변경이 지우고 다시 만드는 것으로 나오면 자료가 사라집니다.
  • 이미 적용된 마이그레이션은 고치지 않고 새로 하나 더 만듭니다.