JD.Efcpt.Build: 빌드 시점 EF Core 스캐폴딩으로 데이터베이스‑퍼스트 모델을 동기화 유지
I’m ready to translate the article for you, but I’ll need the text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll keep the source link at the top exactly as you provided and preserve all formatting, markdown, and code blocks.
소개
Entity Framework를 사용하고 있었고 EF Core가 처음 출시되었을 때, 데이터베이스‑우선 지원을 찾아보았을 때… 아무것도 없다는 것을 기억하실 겁니다.
EF Core는 코드‑우선 프레임워크로 시작했습니다. EF6 개발자들이 의존하던 Reverse Engineer 도구(마우스 오른쪽 클릭 → 데이터베이스 지정 → 모델 생성 워크플로)는 존재하지 않았습니다. 마이크로소프트의 입장은 본질적으로 “마이그레이션이 미래다, 스스로 해결해라.” 라는 것이었습니다.
팀에 기존 데이터베이스가 있거나, 스키마를 실제로 관리하는 DBA가 있거나, 데이터베이스가 진실의 원천이라는 컴플라이언스 요구사항이 있다면… 글쎄요, 행운을 빕니다.
커뮤니티의 반응은 즉각적이고 크게 울려 퍼졌습니다. “데이터베이스‑우선은 어디로 갔나요?” 라는 질문이 GitHub 이슈, Stack Overflow 질문, 그리고 수백 개의 엔티티 클래스를 손수 작성하지 않고도 데이터베이스와 대화하고 싶어하는 개발자들의 조용한 좌절 속에 반복되었습니다.
(임시) 해결책
결국 도구가 따라잡았습니다. EF Core Power Tools가 커뮤니티의 답변으로 등장했으며, 이는 Visual Studio 확장 프로그램으로 역공학 워크플로를 다시 가져왔습니다.
- 데이터베이스 또는 DACPAC에 지정합니다.
- 몇 가지 옵션을 구성합니다.
- 모델을 생성합니다.
문제 해결… 대부분.
수동 프로세스 문제
수동 프로세스는 잘 작동합니다—문제가 생기기 전까지는요.
레거시 데이터 레이어가 있는 코드베이스에서 충분히 작업해 본 사람이라면 패턴을 알아차릴 수 있습니다. 대략 다음과 같습니다:
-
초기 설정 – 누군가 EF Core Power Tools를 설치하고, 초기 모델을 생성한 뒤, 모든 것을 커밋하고, 절차를 문서화합니다:
“스키마가 변경되면, 이 도구와 이 설정을 사용해 모델을 다시 생성하세요.”
충분히 명확합니다. -
시간이 흐름 – 설정을 만든 개발자가 떠납니다. 문서는 오래됩니다.
- 누군가 약간 다른 설정으로 다시 생성하고 결과를 커밋합니다.
- 다른 사람은 스키마 변경 후 모델 생성을 잊어버립니다.
-
드리프트 – 모델이, 설정이 점점 달라지고, “올바른” 재생성 프로세스가 무엇인지 아무도 확신하지 못합니다. 사람들은 그냥… 일관되게 하지 않게 됩니다.
이는 극적인 실패가 아니라 점진적인 침식입니다. 생산 환경에서 디버깅을 하다가 데이터베이스에 6개월째 존재하던 컬럼이 엔티티 클래스에 없다는 사실을 깨달을 때까지는 문제를 알지 못합니다.
오랫동안 같은 코드베이스에서 일했다면, 이런 형태를 한 번쯤은 보았을 겁니다. 드리프트를 발견했을 수도 있고, 직접 일으켰을 수도 있습니다(판단하지 않겠습니다—우리 모두 그런 경험이 있습니다).
답답한 점은 해결책이 언제나 똑같다는 것입니다:
- 모델을 다시 생성합니다.
- 변경 사항을 커밋합니다.
- 스키마 변경 후 모델을 다시 생성하도록 모두에게 상기시킵니다.
…그리고 6개월 뒤 다시 같은 이야기를 하게 됩니다.
실패 모드 (무엇이 잘못되는가)
| 문제 | 설명 |
|---|---|
| 소유권 | 스키마 변경 후 재생성을 누가 담당해야 할까? 스키마 작성자? 데이터 레이어 소유자? 기술 리드? 명확한 답이 없어 → 때로는 모두가 (혼란) 하고, 때로는 아무도 (드리프트) 하지 않는다. |
| 구성 | EF Core Power Tools는 JSON 파일에 설정을 저장한다 (네임스페이스, nullable reference types, 네비게이션 속성 생성, 이름 바꾸기 규칙 등). 수십 개의 옵션 때문에 서로 다른 개발자가 같은 데이터베이스에서 일관되지 않은 결과물을 만들 수 있다. |
| 툴링 | 재생성에는 확장 기능이 설치된 Visual Studio가 필요하다. CI 서버에는 VS가 없고, 새 개발자는 확장 기능을 갖추지 못할 수도 있다. 원격 개발 환경에서는 지원되지 않을 수도 있다. 한 머신에서 동작하는 프로세스가 다른 머신에서도 반드시 동작하는 것은 아니다. |
| 노이즈 | 재생성은 종종 대규모 diff를 만든다: 속성 순서 변경, 공백 변화, 속성 추가 등 – 실제 스키마 변경이 아닌데도 커밋을 어지럽힌다. 개발자는 재생성 diff를 신뢰하지 않게 되고, 실행을 꺼리게 되며 문제는 악화된다. |
| 타이밍 | 모두가 프로세스를 알고 있더라도 강제성이 없다. 모델에 없는 컬럼을 참조하는 코드는 해당 경로가 실행되지 않으면 여전히 컴파일될 수 있다. 오류는 나중에, 원래 스키마 변경과의 연결이 오래 잊혀진 뒤에 나타난다. |
이 문제들은 각각은 치명적이지 않지만, 함께 모이면 이론적으로는 작동하고 실제로는 실패하는 프로세스를 만든다.
더 나은 아이디어: 생성 과정을 빌드에 포함시키기
모델 생성을 명령줄에서 호출할 수 있다면(EF Core Power Tools CLI를 통해 가능), 빌드의 일부가 될 수 있습니다.
- 별도로 기억해 두고 실행해야 하는 단계가 아닙니다.
- 소유권이 불분명한 수동 프로세스가 아닙니다.
dotnet build를 실행할 때 자동으로 일어나는 일입니다.
빌드는 이미 다음을 수행하는 방법을 알고 있습니다:
- 패키지 복원.
- 분석기 실행.
- 아티팩트 생성.
그 목록에 “스키마에서 EF Core 모델 생성”을 추가하는 것은 다른 빌드‑시점 코드 생성과 개념적으로 차이가 없습니다.
- 소유권이 사라짐 – 빌드가 이를 소유합니다.
- 구성 드리프트가 사라짐 – 빌드가 단일하고 일관된 구성을 사용합니다.
- 툴링 문제가 사라짐 – 모든 머신이 동일한 MSBuild 타깃을 실행하므로 별도의 확장이 필요 없습니다.
JD.Efcpt.Build 소개
JD.Efcpt.Build는 EF Core 모델 생성을 자동화하는 MSBuild 통합입니다.
작동 방식
- 스키마 소스 찾기 – DACPAC으로 컴파일되는 SQL Server 데이터베이스 프로젝트(
.sqlproj)이든, 실시간 데이터베이스를 가리키는 연결 문자열이든. - 지문 계산 – 모든 입력(DACPAC 또는 스키마 메타데이터, 구성 파일, 이름 바꾸기 규칙, 사용자 정의 템플릿)의 해시. 이 지문은 “전체 현재 상태”를 나타냅니다.
- 변경 감지 – 지문이 이전 빌드와 다르면 대상이 생성 단계를 실행하고, 그렇지 않으면 건너뜁니다.
- 모델 생성 – 저장된 구성으로 EF Core Power Tools CLI를 호출하여 지정된 폴더에 엔티티 클래스를 생성합니다.
- 생성된 파일을 컴파일에 추가 – 생성된
.cs파일이 프로젝트에 자동으로 포함되어 나머지 코드와 함께 컴파일됩니다.
장점
| Benefit | Explanation |
|---|---|
| 결정론적 출력 | 동일한 입력 → 모든 머신에서 동일한 생성 코드가 나옵니다. |
| 수동 작업 제로 | VS 확장을 실행해야 한다는 기억이 필요 없으며, 빌드가 자동으로 수행합니다. |
| CI 친화적 | 헤드리스 에이전트에서도 동작하며, Visual Studio가 필요 없습니다. |
| 명확한 소유권 | 빌드가 재생성을 담당하고, 개발자는 생성된 파일만 커밋합니다. |
| 노이즈 감소 | 지문이 변경될 때만 생성이 실행되므로, 차이점은 실제 스키마 변경에만 제한됩니다. |
| 구성 가능 | 모든 Power‑Tools 옵션이 소스 제어에 포함된 간단한 JSON 파일을 통해 노출됩니다. |
시작하기
# Add the package to your project
dotnet add package JD.Efcpt.Build
프로젝트 루트에 efcpt.json(또는 원하는 이름) 파일을 생성합니다:
{
"ConnectionString": "Server=.;Database=MyDb;Trusted_Connection=True;",
"OutputDirectory": "GeneratedModels",
"Namespace": "MyApp.Data.Models",
"UseNullableReferenceTypes": true,
"GenerateDataAnnotations": true,
"RenamingRules": {
"Tables": { "tbl_": "" },
"Columns": { "col_": "" }
}
}
패키지는 빌드 중에 실행되는 MSBuild 타깃을 자동으로 추가합니다. EfcptEnabled 속성을 설정하여 구성별(예: CI에서만)로 비활성화할 수 있습니다.
Summary
-
Database‑first support disappeared in early EF Core releases, leading to community frustration.
→ 초기 EF Core 릴리스에서 데이터베이스‑퍼스트 지원이 사라져 커뮤니티에 불만이 생겼습니다. -
EF Core Power Tools filled the gap, but manual regeneration introduced ownership, configuration, tooling, noise, and timing problems.
→ EF Core Power Tools가 그 공백을 메웠지만, 수동 재생성은 소유권, 구성, 도구, 잡음 및 타이밍 문제를 야기했습니다. -
Embedding generation in the build eliminates those problems.
→ 빌드에 생성 과정을 포함하면 이러한 문제들을 제거할 수 있습니다. -
JD.Efcpt.Buildprovides a lightweight, deterministic, CI‑friendly solution that makes EF Core model generation a first‑class part of your build pipeline.
→JD.Efcpt.Build는 가볍고 결정론적이며 CI 친화적인 솔루션을 제공하여 EF Core 모델 생성을 빌드 파이프라인의 핵심 요소로 만듭니다.
Give it a try, and let the build keep your models in sync with the schema—once and for all.
→ 한 번 사용해 보세요. 빌드가 스키마와 모델을 한 번에 완전히 동기화하도록 하세요.
개요
이 패키지는 빌드 중에 EF Core 모델 생성을 자동화합니다.
- 입력값의 지문(XxHash64 사용)을 비교하여 변경 사항을 감지합니다.
- 지문이 이전 실행과 일치하면 생성을 건너뛰어—증분 빌드에 대한 오버헤드가 없습니다.
- 입력값이 실제로 변경될 때만 생성을 실행합니다.
수행 작업
- EF Core Power Tools CLI를 사용해 모델을 생성합니다(수동으로 실행하는 명령과 동일).
- 출력 위치:
obj/efcpt/Generated/(확장자는.g.cs).
- 출력 위치:
- 생성된 파일을 자동으로 컴파일에 포함합니다—프로젝트 파일을 수정하거나 포함 항목을 관리할 필요가 없습니다.
모드
다양한 팀이 데이터베이스 스키마를 관리하는 방식이 다르기 때문에, 이 패키지는 두 가지 모드를 지원합니다.
DACPAC 모드
- SQL Server Database Projects (
.sqlproj)를 사용하는 팀을 위해. - 프로젝트는 버전 관리된 SQL 파일에 스키마를 정의합니다.
- 패키지는 프로젝트를 빌드하고 DACPAC을 생성한 뒤, 이를 기반으로 모델을 만듭니다.
..\Database\MyDatabase.sqlproj
장점
- 스키마가 소스 컨트롤에 보관됩니다.
- 변경 사항이 풀 리퀘스트를 통해 진행됩니다.
- DACPAC은 결정론적인 빌드 아티팩트입니다.
연결 문자열 모드
- 데이터베이스 프로젝트가 없는 팀을 위해.
- 개발 데이터베이스, 클라우드 데이터베이스에서 스캐폴딩하거나 단순히 DACPAC을 원하지 않을 때 유용합니다.
$(DB_CONNECTION_STRING)
- 패키지는 연결하고 시스템 테이블을 조회하여 스키마 메타데이터로부터 모델을 생성합니다.
- 지문은 해당 메타데이터를 기반으로 계산되므로 증분 빌드가 여전히 작동합니다.
두 모드 모두 동일한 구성 파일을 사용하고 동일한 출력을 생성합니다; 차이점은 스키마가 어디서 오는가에만 있습니다.
최소 설정
패키지 참조를 추가합니다:
<PackageReference Include="JD.Efcpt.Build" Version="x.y.z" />
솔루션에 .sqlproj 파일이 및 프로젝트 디렉터리에 efcpt-config.json 파일이 있으면, 그것만으로 충분합니다. dotnet build를 실행하면 모델이 나타납니다.
Configuration (efcpt-config.json)
{
"names": {
"root-namespace": "MyApp.Data",
"dbcontext-name": "ApplicationDbContext"
},
"code-generation": {
"use-nullable-reference-types": true,
"enable-on-configuring": false
}
}
enable-on-configuring: false→ 생성된DbContext에 하드코딩된 연결 문자열이 포함되지 않으며; DI 컨테이너에서 직접 구성합니다.
Renaming Rules
데이터베이스 명명 규칙이 C#에 깔끔하게 매핑되지 않는 경우, 이름 바꾸기 규칙을 추가합니다:
[
{
"SchemaName": "dbo",
"Tables": [
{
"Name": "tbl_Users",
"NewName": "User",
"Columns": [
{ "Name": "user_id", "NewName": "Id" }
]
}
]
}
]
Result: tbl_Users.user_id → User.Id. 데이터베이스는 기존 규칙을 유지하고, C# 코드는 자체 규칙을 따르며, 매핑은 버전 관리됩니다.
Partial Classes – Custom Logic 안전하게 유지하기
Generated entity (auto‑generated, regenerated each build):
// obj/efcpt/Generated/User.g.cs
public partial class User
{
public int Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Your custom logic (never overwritten):
// Models/User.cs
public partial class User
{
public string FullName => $"{FirstName} {LastName}";
public bool HasValidEmail => Email?.Contains("@") ?? false;
}
두 부분이 하나의 클래스로 컴파일됩니다. 이와 같이 분리하면 자동 생성 코드와 수동으로 작성한 코드를 한 파일에 섞는 것보다 훨씬 깔끔합니다.
CI/CD – 제로 터치 통합
Manual regeneration doesn’t work well in pipelines. With build‑time generation, CI just builds:
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Build
run: dotnet build --configuration Release
env:
DB_CONNECTION_STRING: ${{ secrets.DB_CONNECTION_STRING }}
- EF Core 생성에 대한 추가 단계가 없습니다.
- .NET 10+에서는 패키지가
dotnet dnx를 사용하여 패키지 피드에서 직접 도구를 실행합니다 (전역 도구 설치가 필요 없음). - 이전 버전에서는 도구 매니페스트 또는 전역 도구로 대체됩니다.
스키마를 수정하는 풀 리퀘스트는 자동으로 해당 모델 변경을 생성하여 스키마와 코드가 동기화된 상태를 유지합니다.
디버깅 및 진단
상세 로깅
<LogLevel>detailed</LogLevel>
빌드 출력 은 이제 어떤 입력이 발견되었는지, 계산된 지문, 그리고 생성이 실행되었는지 혹은 건너뛰었는지를 보여줍니다.
해결된 입력 검사
빌드 후 obj/efcpt/resolved-inputs.json을 엽니다. 이 파일은 각 모드에 대해 패키지가 입력으로 간주한 내용을 정확히 나열합니다.
지문 검사
obj/efcpt/fingerprint.txt에는 현재 지문이 들어 있습니다. 이전 실행과 비교하여 생성이 실행된 이유(또는 실행되지 않은 이유)를 이해하십시오.
Final Note
The package is designed to be practical: fast fingerprint checks keep incremental builds cheap, and the generated code lives in a predictable location (obj/efcpt/Generated). Use partial classes for custom logic, configure the mode that matches your workflow, and let the build handle everything else.
대상 독자
- Database‑first development에서 regeneration coordination 문제에 직면한 경우.
- 스키마를 자주 변경하는 프로젝트(예: 주간 릴리스)이며 수동 재생성이 마찰의 원인인 경우.
- 빌드가 모든 환경에서 동일하게 동작해야 하는 팀 – 로컬 머신, CI 서버, 새로운 개발자 노트북 – 모든 개발자가 동일한 입력으로부터 동일한 생성 코드를 얻을 수 있도록.
이 경우는 해당되지 않을 가능성이 높은 경우
- 본질적으로 정적인 스키마; 변경이 드물어 수동 재생성이 허용됩니다.
- 코드‑퍼스트 마이그레이션을 진실의 원천으로 사용하는 경우 – 다른 문제 영역.
- EF Core Power Tools를 사용하지 않는 프로젝트; 이 패키지는 해당 도구를 자동화하므로 다른 생성 접근 방식은 적용되지 않습니다.