다음을 통해 공유


팀 환경의 Code First 마이그레이션

참고 항목

이 문서에서는 기본 시나리오에서 Code First 마이그레이션을 사용하는 방법을 알고 있다고 가정합니다. 방법을 모르는 경우, 계속하기 전에 Code First 마이그레이션을 읽어야 합니다.

커피를 마시며 이 전체 문서를 읽어보세요

팀 환경의 문제는 주로 두 개발자가 로컬 코드 베이스에서 마이그레이션을 생성했을 때 마이그레이션 병합과 관련이 있습니다. 이러한 문제를 해결하는 단계는 매우 간단하지만 마이그레이션의 작동 방식을 확실하게 이해해야 합니다. 끝까지 건너뛰지 말고 시간을 내어 전체 문서를 읽어 성공적으로 진행해 보세요.

몇 가지 일반 지침

여러 개발자가 생성한 병합 마이그레이션을 관리하는 방법을 자세히 알아보기 전에 성공을 위한 몇 가지 일반 지침을 알아보겠습니다.

각 팀 구성원은 로컬 개발 데이터베이스를 가지고 있어야 함

마이그레이션은 __MigrationsHistory 테이블을 사용하여 데이터베이스에 적용된 마이그레이션을 저장합니다. 여러 개발자가 동일한 데이터베이스를 대상으로 하여 서로 다른 마이그레이션을 생성하면(따라서 __MigrationsHistory 테이블을 공유함) 마이그레이션은 매우 혼란스러울 것입니다.

물론 마이그레이션을 생성하지 않는 팀 구성원이 있으면 중앙 개발 데이터베이스를 공유하는 데 문제가 없습니다.

자동 마이그레이션 방지

요점은 자동 마이그레이션이 처음에는 팀 환경에서 잘 보이지만 실제로는 작동하지 않는다는 것입니다. 이유를 알고 싶다면 계속 읽어보세요. 그렇지 않으면 다음 섹션으로 건너뛰면 됩니다.

자동 마이그레이션을 사용하면 코드 파일(코드 기반 마이그레이션)을 생성할 필요 없이 현재 모델과 일치하도록 데이터베이스 스키마를 업데이트할 수 있습니다. 자동 마이그레이션은 사용자만 사용하고 코드 기반 마이그레이션을 생성하지 않은 경우에 팀 환경에서 매우 잘 작동합니다. 문제는 자동 마이그레이션이 제한되어 있으며 속성/열 이름 바꾸기, 다른 테이블로 데이터 이동 등의 여러 작업을 처리하지 않는다는 것입니다. 이러한 시나리오를 처리하려면 자동 마이그레이션으로 처리되는 변경 내용 간에 혼합된 코드 기반 마이그레이션을 생성(및 스캐폴드된 코드 편집)해야 합니다. 따라서 두 개발자가 마이그레이션을 체크 인할 때 변경 내용을 병합하는 것이 거의 불가능합니다.

마이그레이션 작동 방식 이해

팀 환경에서 마이그레이션을 성공적으로 사용하기 위한 핵심 사항은 마이그레이션이 모델에 대한 정보를 추적하고 사용하여 모델 변경 내용을 검색하는 방법을 기본적으로 이해하는 것입니다.

첫 번째 마이그레이션

프로젝트에 첫 번째 마이그레이션을 추가할 때 패키지 관리자 콘솔에서 Add-Migration First와 같은 작업을 실행합니다. 이 명령이 수행하는 개략적인 단계는 아래 그림과 같습니다.

첫 번째 마이그레이션

현재 모델은 코드에서 계산됩니다(1). 그런 다음, 필요한 데이터베이스 개체가 모델 차이에 따라 계산됩니다(2). 이는 첫 번째 마이그레이션이기 때문에 모델이 다르므로 비교를 위해 빈 모델을 사용합니다. 필요한 변경 내용은 코드 생성기에 전달되어 필요한 마이그레이션 코드를 빌드한 다음(3), Visual Studio 솔루션에 추가됩니다(4).

기본 코드 파일에 저장된 실제 마이그레이션 코드 외에도 마이그레이션에서는 몇 가지 추가 코드 숨김 파일도 생성합니다. 이러한 파일은 마이그레이션에 사용되는 메타데이터이므로 편집하지 않아야 합니다. 이러한 파일 중 하나는 마이그레이션이 생성된 당시 모델의 스냅샷이 포함된 리소스 파일(.resx)입니다. 다음 단계에서 이 파일이 어떻게 사용되는지 살펴보겠습니다.

이 시점에서는 Update-Database를 실행하여 변경 내용을 데이터베이스에 적용한 다음, 애플리케이션의 다른 영역을 구현하는 작업을 알아볼 것입니다.

후속 마이그레이션

나중에 돌아와서 모델을 일부 변경합니다. 이 예제에서는 블로그URL 속성을 추가하겠습니다. 그런 다음 Add-Migration AddUrl과 같은 명령을 실행하여 해당 데이터베이스 변경 내용을 적용하기 위한 마이그레이션을 스캐폴드합니다. 이 명령이 수행하는 개략적인 단계는 아래 그림과 같습니다.

두 번째 마이그레이션

지난번과 마찬가지로 현재 모델은 코드에서 계산됩니다(1). 그러나 이번에는 기존 마이그레이션이 있으므로 최신 마이그레이션에서 이전 모델을 검색합니다(2). 필요한 데이터베이스 변경 내용을 찾기 위해 이러한 두 모델을 비교한 다음(3), 프로세스가 이전과 같이 완료됩니다.

프로젝트에 추가하는 추가 마이그레이션에도 이와 동일한 프로세스가 사용됩니다.

모델 스냅샷을 사용하는 이유는 무엇인가요?

EF가 데이터베이스만 살펴보는 것이 아니라 굳이 모델 스냅샷을 사용하는 이유가 무엇인지 궁금할 것입니다. 그렇다면 계속 읽어보세요. 관심이 없는 내용이라면 이 섹션을 건너뛰어도 됩니다.

EF에서 모델 스냅샷을 계속 살펴보는 데는 여러 가지 이유가 있습니다.

  • 데이터베이스를 EF 모델에서 드리프트할 수 있습니다. 이러한 변경은 데이터베이스에서 직접 수행할 수도 있고 마이그레이션에서 스캐폴드 코드를 변경하여 수행할 수도 있습니다. 이에 대한 몇 가지 실제 예는 다음과 같습니다.
    • 하나 이상의 테이블에 Inserted 및 Updated 열을 추가하려고 하지만 EF 모델에는 이러한 열을 포함하고 싶지 않습니다. 마이그레이션에서 데이터베이스를 보면 마이그레이션을 스캐폴드할 때마다 해당 열이 지속적으로 삭제 시도됩니다. 모델 스냅샷을 사용하면 EF는 모델에 대한 적법한 변경 내용만 검색합니다.
    • 일부 로깅을 포함하도록 업데이트에 사용되는 저장 프로시저의 본문을 변경하려고 합니다. 마이그레이션이 데이터베이스에서 이 저장 프로시저를 살펴본 경우 계속해서 EF가 예상하는 정의로 다시 재설정하려고 합니다. 모델 스냅샷을 사용하면 EF는 EF 모델에서 프로시저의 모양을 변경할 때 저장 프로시저를 변경하기 위한 스캐폴드 코드만 사용합니다.
    • 데이터베이스에 추가 테이블을 포함하여 또 다른 인덱스를 추가하고, 테이블 위에 있는 데이터베이스 뷰에 EF를 매핑하는 등의 작업에도 동일한 원칙이 적용됩니다.
  • EF 모델에는 데이터베이스의 모양 이상이 포함됩니다. 전체 모델이 있으면 마이그레이션 시 모델의 속성 및 클래스에 대한 정보와 열 및 테이블에 이러한 정보가 매핑되는 방식을 확인할 수 있습니다. 이 정보를 통해 마이그레이션이 스캐폴드되는 코드에서 더욱 지능적으로 마이그레이션할 수 있습니다. 예를 들어 속성이 마이그레이션에 매핑되는 열의 이름을 변경하면 동일한 속성인지 확인하여 이름 변경을 감지할 수 있습니다. 데이터베이스 스키마만 있는 경우에는 수행할 수 없는 작업입니다. 

팀 환경에서 문제를 일으키는 원인

이전 섹션에서 다룬 워크플로는 단일 개발자가 애플리케이션을 작업할 때 효과적입니다. 사용자가 모델을 변경하는 유일한 사람인 경우 팀 환경에서도 잘 작동합니다. 이 시나리오에서는 모델을 변경하고 마이그레이션을 생성하여 소스 제어에 제출할 수 있습니다. 다른 개발자는 변경 내용을 동기화하고 Update-Database를 실행하여 스키마 변경 내용을 적용할 수 있습니다.

여러 개발자가 EF 모델을 변경하고 동시에 소스 제어에 제출하면 문제가 발생하기 시작합니다. EF에는 마지막 동기화 이후 다른 개발자가 소스 제어에 제출한 마이그레이션과 로컬 마이그레이션을 병합하는 최적의 방법이 없습니다.

병합 충돌의 예

먼저 이러한 병합 충돌의 구체적인 예제를 살펴보겠습니다. 앞서 살펴본 예제를 계속 살펴보겠습니다. 우선 원래 개발자가 이전 섹션의 변경 내용을 체크 인했다고 가정해 보겠습니다. 두 명의 개발자가 코드 베이스를 변경하는 과정을 추적합니다.

다양한 변경 내용을 통해 EF 모델과 마이그레이션을 추적합니다. 시작점으로, 두 개발자는 다음과 같은 그래픽에 설명된 대로 소스 제어 리포지토리에 동기화했습니다.

시작 지점

이제 개발자 #1과 개발자 #2는 로컬 코드 베이스에서 EF 모델을 일부 변경합니다. 개발자 #1은 블로그Rating 속성을 추가하고 AddRating 마이그레이션을 생성하여 변경 내용을 데이터베이스에 적용합니다. 개발자 #2는 블로그Readers 속성을 추가하고 해당하는 AddReaders 마이그레이션을 생성합니다. 두 개발자 모두 Update-Database를 실행하여 변경 내용을 로컬 데이터베이스에 적용한 다음, 계속해서 애플리케이션을 개발합니다.

참고 항목

마이그레이션에는 타임스탬프가 접두사로 사용되므로 그래픽에서는 개발자 #2의 AddReaders 마이그레이션이 개발자 #1의 AddRating 마이그레이션 이후에 발생함을 나타냅니다. 개발자 #1 또는 #2가 마이그레이션을 먼저 생성했는지 여부는 팀에서 작업하는 문제나 다음 섹션에서 살펴보게 될 병합 프로세스에 아무런 차이가 없습니다.

로컬 변경 내용

개발자 #1이 변경 내용을 먼저 제출하므로 운이 좋은 날입니다. 리포지토리를 동기화한 이후에는 아무도 체크 인하지 않았으므로 병합을 수행하지 않고 변경 내용만 제출하면 됩니다.

변경 내용 전송

이제 개발자 #2가 제출할 시간입니다. 이들은 그렇게 운이 좋지는 않습니다. 동기화된 이후 다른 사람이 변경 내용을 제출했으므로 변경 내용을 풀다운하여 병합해야 합니다. 소스 제어 시스템은 매우 간단하기 때문에 코드 수준에서 변경 내용을 자동으로 병합할 수 있습니다. 동기화 후 개발자 #2의 로컬 리포지토리 상태는 다음 그래픽에 나와 있습니다. 

소스 제어에서 끌어오기

이 단계에서 개발자 #2는 새로운 AddRating 마이그레이션(개발자 #2의 데이터베이스에 적용되지 않은)을 감지하고 적용하는 Update-Database를 실행할 수 있습니다. 이제 Rating 열이 블로그 테이블에 추가되고 데이터베이스가 모델과 동기화됩니다.

하지만 다음과 같은 몇 가지 문제가 있습니다.

  1. Update-DatabaseAddRating 마이그레이션을 적용하지만 경고도 표시됩니다. 보류 중인 변경 내용이 있고 자동 마이그레이션이 사용하지 않도록 설정되어 있으므로 현재 모델과 일치하도록 데이터베이스를 업데이트할 수 없습니다. … 문제는 마지막 마이그레이션(AddReader)에 저장된 모델 스냅샷에 블로그Rating 속성이 없다는 것입니다(마이그레이션이 생성될 때 모델의 일부가 아니었기 때문). Code First는 마지막 마이그레이션의 모델이 현재 모델과 일치하지 않음을 감지하고 경고를 표시합니다.
  2. 애플리케이션을 실행하면 "데이터베이스가 만들어진 이후 'BloggingContext' 컨텍스트를 지원하는 모델이 변경되었습니다. 데이터베이스를 업데이트하려면 Code First 마이그레이션을 사용해 보세요...”라는 InvalidOperationException이 발생합니다. 이번에도 문제는 마지막 마이그레이션에 저장된 모델 스냅샷이 현재 모델과 일치하지 않는다는 것입니다.
  3. 마지막으로 이제 Add-Migration을 실행하면 빈 마이그레이션이 생성될 것으로 예상됩니다(데이터베이스에 적용할 변경 내용이 없으므로). 그러나 마이그레이션은 현재 모델을 마지막 마이그레이션의 모델(Rating 속성 없음)과 비교하기 때문에 실제로는 Rating에 추가하기 위해 또 다른 AddColumn 호출을 스캐폴드합니다. 물론 Rating 열이 이미 있으므로 Update-Database 중에는 이 마이그레이션이 실패합니다.

병합 충돌 해결

좋은 점은 마이그레이션의 작동 방식을 이해하고 있다면 병합을 수동으로 처리하는 것이 그리 어렵지 않다는 것입니다. 따라서 이 섹션을 건너뛰었다면... 죄송합니다. 먼저 앞으로 돌아가서 문서의 나머지 부분을 읽어보세요!

두 가지 옵션이 있습니다. 가장 쉬운 방법은 올바른 현재 모델을 스냅샷으로 포함하는 빈 마이그레이션을 생성하는 것입니다. 두 번째 옵션은 올바른 모델 스냅샷을 갖도록 마지막 마이그레이션의 스냅샷을 업데이트하는 것입니다. 두 번째 옵션은 조금 더 어렵고 모든 시나리오에서 사용할 수는 없지만 또 다른 마이그레이션을 추가하지 않기 때문에 더 깔끔합니다.

옵션 1: 빈 '병합' 마이그레이션 추가

이 옵션에서는 최신 마이그레이션에 올바른 모델 스냅샷이 저장되어 있는지 확인하기 위한 목적으로만 빈 마이그레이션을 생성합니다.

이 옵션은 마지막 마이그레이션을 생성한 사람에 관계없이 사용할 수 있습니다. 살펴보는 이 예제에서는 개발자 #2가 병합을 관리하고 있으며 마지막 마이그레이션을 생성했습니다. 그러나 개발자 #1이 마지막 마이그레이션을 생성한 경우에도 이와 동일한 단계를 사용할 수 있습니다. 이 단계는 여러 마이그레이션이 관련된 경우에도 적용됩니다. 여기서는 간단하게 유지하기 위해 두 가지만 살펴보았습니다.

이 접근 방식에는 소스 제어에서 동기화해야 하는 변경 내용이 있다는 것을 알게 된 시점부터 다음 프로세스를 사용할 수 있습니다.

  1. 로컬 코드 베이스의 보류 중인 모델 변경 내용이 마이그레이션에 기록되었는지 확인합니다. 이 단계를 통해 빈 마이그레이션을 생성할 때 적법한 변경 내용을 놓치지 않도록 할 수 있습니다.
  2. 소스 제어와 동기화합니다.
  3. 다른 개발자가 체크 인한 새 마이그레이션을 적용하려면 Update-Database를 실행합니다. 참고:Update-Database 명령에서 경고가 표시되지 않으면 다른 개발자의 새로운 마이그레이션이 없었으므로 더 이상 병합을 수행할 필요가 없습니다.
  4. Add-Migration <pick_a_name> –IgnoreChanges(예: Add-Migration Merge –IgnoreChanges)를 실행합니다. 이렇게 하면 모든 메타데이터(현재 모델의 스냅샷 포함)가 포함된 마이그레이션이 생성되지만 현재 모델을 마지막 마이그레이션의 스냅샷과 비교할 때 감지된 모든 변경 내용은 무시됩니다(즉, 빈 UpDown 메서드가 있음).
  5. 업데이트된 메타데이터로 최신 마이그레이션을 다시 적용하려면 Update-Database를 실행합니다.
  6. 계속해서 개발하거나 소스 제어에 제출합니다(물론 단위 테스트를 실행한 후에).

이 접근 방식을 사용한 후 개발자 #2의 로컬 코드 베이스 상태는 다음과 같습니다.

병합 마이그레이션

옵션 2: 마지막 마이그레이션에서 모델 스냅샷 업데이트

이 옵션은 옵션 1과 매우 비슷하지만 또 다른 빈 마이그레이션을 제거합니다. 왜냐하면 솔루션에 또 다른 코드 파일을 원하는 사람이 있기 때문입니다.

이 접근 방식은 최신 마이그레이션이 로컬 코드 베이스에만 있고 소스 제어에 아직 제출되지 않은 경우에만 가능합니다(예: 병합을 수행하는 사용자가 마지막 마이그레이션을 생성한 경우). 다른 개발자가 이미 개발 데이터베이스에 적용했거나 심지어 프로덕션 데이터베이스에 적용했을 수도 있는 마이그레이션의 메타데이터를 편집하면 예상치 못한 부작용이 발생할 수 있습니다. 이 과정에서는 로컬 데이터베이스의 마지막 마이그레이션을 롤백하고 업데이트된 메타데이터로 다시 적용할 것입니다.

마지막 마이그레이션은 로컬 코드 베이스에 있어야 하지만 이를 진행하는 마이그레이션의 수나 순서에는 제한이 없습니다. 여러 개발자가 여러 번 마이그레이션할 수 있으며 동일한 단계가 적용됩니다. 여기서는 간단하게 하기 위해 두 가지만 살펴보았습니다.

이 접근 방식에는 소스 제어에서 동기화해야 하는 변경 내용이 있다는 것을 알게 된 시점부터 다음 프로세스를 사용할 수 있습니다.

  1. 로컬 코드 베이스의 보류 중인 모델 변경 내용이 마이그레이션에 기록되었는지 확인합니다. 이 단계를 통해 빈 마이그레이션을 생성할 때 적법한 변경 내용을 놓치지 않도록 할 수 있습니다.
  2. 소스 제어와 동기화합니다.
  3. 다른 개발자가 체크 인한 새 마이그레이션을 적용하려면 Update-Database를 실행합니다. 참고:Update-Database 명령에서 경고가 표시되지 않으면 다른 개발자의 새로운 마이그레이션이 없었으므로 더 이상 병합을 수행할 필요가 없습니다.
  4. Update-Database –TargetMigration <second_last_migration>을 실행합니다(살펴보는 예제에서는 Update-Database –TargetMigration AddRating과 유사함). 이렇게 하면 데이터베이스가 마지막 두 번째 마이그레이션 상태로 롤백됩니다. 즉, 데이터베이스에서 마지막 마이그레이션을 효과적으로 '적용 취소'합니다. 참고: 메타데이터는 데이터베이스의 __MigrationsHistoryTable에도 저장되기 때문에 마이그레이션의 메타데이터를 안전하게 편집하려면 이 단계가 필요합니다. 이것이 바로 마지막 마이그레이션이 로컬 코드 베이스에 있는 경우에만 이 옵션을 사용해야 하는 이유입니다. 다른 데이터베이스에 마지막 마이그레이션이 적용된 경우 이를 롤백하고 마지막 마이그레이션을 다시 적용하여 메타데이터를 업데이트해야 합니다. 
  5. Add-Migration <full_name_including_timestamp_of_last_migration>을 실행합니다(살펴보는 예제에서는 Add-Migration 201311062215252_AddReaders와 유사함). 참고: 새 마이그레이션을 스캐폴드하는 대신 기존 마이그레이션을 편집하려는 것을 마이그레이션에서 알 수 있도록 타임스탬프를 포함해야 합니다. 그러면 현재 모델과 일치하도록 마지막 마이그레이션의 메타데이터가 업데이트됩니다. 명령이 완료되면 다음과 같은 경고가 표시되지만 이것이 바로 원하는 것입니다. "'201311062215252_AddReaders' 마이그레이션에 대한 Designer Code만 다시 스캐폴드되었습니다. 전체 마이그레이션을 다시 스캐폴드하려면 Force 매개 변수를 사용하세요."
  6. 업데이트된 메타데이터로 최신 마이그레이션을 다시 적용하려면 Update-Database를 실행합니다.
  7. 계속해서 개발하거나 소스 제어에 제출합니다(물론 단위 테스트를 실행한 후에).

이 접근 방식을 사용한 후 개발자 #2의 로컬 코드 베이스 상태는 다음과 같습니다.

업데이트된 메타데이터

요약

팀 환경에서 Code First 마이그레이션을 사용할 때 몇 가지 문제가 있습니다. 그러나 마이그레이션 작동 방식에 대한 기본적인 이해와 병합 충돌을 해결하기 위한 몇 가지 간단한 접근 방식을 통해 이러한 문제를 쉽게 극복할 수 있습니다.

근본적인 문제는 최신 마이그레이션에 저장된 잘못된 메타데이터입니다. 이로 인해 Code First는 현재 모델과 데이터베이스 스키마가 일치하지 않음을 잘못 감지하고, 다음 마이그레이션에서 잘못된 코드를 스캐폴드합니다. 이 상황은 올바른 모델로 빈 마이그레이션을 생성하거나 최신 마이그레이션에서 메타데이터를 업데이트하여 해소할 수 있습니다.