다음을 통해 공유


사용자 지정 리버스 엔지니어링 템플릿

비고

이 기능은 EF Core 7에 추가되었습니다.

리버스 엔지니어링을 수행하는 동안 Entity Framework Core는 다양한 앱 유형에서 사용할 수 있는 좋은 범용 코드를 스캐폴딩하고 일관된 모양과 친숙한 느낌을 위해 일반적인 코딩 규칙을 사용합니다. 그러나 더 특수화된 코드와 대체 코딩 스타일이 바람직한 경우도 있습니다. 이 문서에서는 T4 텍스트 템플릿을 사용하여 스캐폴드된 코드를 사용자 지정하는 방법을 보여 줍니다.

필수 조건

이 문서에서는 EF Core의 리버스 엔지니어링에 익숙하다고 가정합니다. 그렇지 않은 경우 계속하기 전에 해당 문서를 검토하세요.

기본 템플릿 추가

스캐폴드된 코드를 사용자 지정하는 첫 번째 단계는 기본 템플릿을 프로젝트에 추가하는 것입니다. 기본 템플릿은 리버스 엔지니어링 시 EF Core에서 내부적으로 사용하는 템플릿입니다. 스캐폴드된 코드 사용자 지정을 시작할 수 있는 시작점을 제공합니다.

EF Core 템플릿 패키지를 먼저 설치합니다.dotnet new

dotnet new install Microsoft.EntityFrameworkCore.Templates

이제 프로젝트에 기본 템플릿을 추가할 수 있습니다. 프로젝트 디렉터리에서 다음 명령을 실행하여 이 작업을 수행합니다.

dotnet new ef-templates

이 명령은 프로젝트에 다음 파일을 추가합니다.

  • CodeTemplates/
    • EFCore/
      • DbContext.t4
      • EntityType.t4

템플릿은 DbContext.t4 데이터베이스에 대한 DbContext 클래스를 스캐폴드하는 데 사용되며, 템플릿은 데이터베이스의 EntityType.t4 각 테이블 및 뷰에 대한 엔터티 형식 클래스를 스캐폴드하는 데 사용됩니다.

팁 (조언)

.t4 확장은 Visual Studio가 템플릿을 변환하지 못하도록 하기 위해 .tt 대신 사용됩니다. 템플릿은 대신 EF Core에서 변환됩니다.

T4 소개

템플릿을 DbContext.t4 열고 해당 내용을 검사해 보겠습니다. 이 파일은 T4 텍스트 템플릿입니다. T4는 .NET을 사용하여 텍스트를 생성하는 언어입니다. 다음 코드는 설명용으로만 사용됩니다. 파일의 전체 내용을 나타내지 않습니다.

중요합니다

특히 코드를 생성하는 T4 텍스트 템플릿은 구문을 강조 표시하지 않고 읽기 어려울 수 있습니다. 필요한 경우 T4 구문 강조 표시를 사용하도록 설정하는 코드 편집기 확장을 검색합니다.

<#@ template hostSpecific="true" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Design" #>
<#@ parameter name="NamespaceHint" type="System.String" #>
<#@ import namespace="Microsoft.EntityFrameworkCore" #>
<#
    if (!string.IsNullOrEmpty(NamespaceHint))
    {
#>
namespace <#= NamespaceHint #>;

처음 몇 줄은 <#@ 지시문이라고 합니다. 템플릿을 변환하는 방법에 영향을 미칩니다. 다음 표에서는 사용되는 각 종류의 지시문을 간략하게 설명합니다.

지시 설명
template 템플릿 내의 Host 속성을 사용하여 EF Core 서비스에 액세스할 수 있도록 하는 hostSpecific="true"를 지정합니다.
assembly 템플릿을 컴파일하는 데 필요한 어셈블리 참조를 추가합니다.
parameter 템플릿을 변환할 때 EF Core에서 전달할 매개 변수를 선언합니다.
import 지시문을 사용하는 C#처럼 네임스페이스를 템플릿 코드의 범위로 가져옵니다.

지시문 다음에 다음 섹션 DbContext.t4 을 제어 블록이라고 합니다. 표준 제어 블록은 .로 <# 시작하고 끝납니다 #>. 템플릿을 변환할 때 내부 코드가 실행됩니다. 제어 블록 내에서 사용할 수 있는 속성 및 메서드 목록은 TextTransformation 클래스를 참조하세요.

컨트롤 블록 외부의 모든 항목은 템플릿 출력에 직접 복사됩니다.

식 제어 블록은 .로 <#=시작합니다. 내부 코드가 평가되고 결과가 템플릿 출력에 추가됩니다. 이들은 C#에서 보간 문자열 인수와 유사합니다.

T4 구문에 대한 자세한 내용과 전체 설명은 T4 텍스트 템플릿 작성을 참조하세요.

엔터티 형식 사용자 지정

템플릿을 사용자 지정하는 방법을 살펴보겠습니다. 기본적으로 EF Core는 컬렉션 탐색 속성에 대해 다음 코드를 생성합니다.

public virtual ICollection<Album> Albums { get; } = new List<Album>();

사용 List<T> 은 대부분의 애플리케이션에 좋은 기본값입니다. XAML 기반 프레임워크인 WPF, WinUI, 또는 .NET MAUI를 사용하는 경우, ObservableCollection<T>을 사용하여 데이터 바인딩을 가능하게 하는 것이 자주 필요합니다.

템플릿을 열고 List<T>를 생성하는 위치를 찾습니다 EntityType.t4. 다음과 같습니다.

    if (navigation.IsCollection)
    {
#>
    public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; } = new List<<#= targetType #>>();
<#
    }

목록을 ObservableCollection으로 대체합니다.

public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; } = new ObservableCollection<<#= targetType #>>();

또한 스캐폴드된 코드에 using 지시문을 추가해야 합니다. usings는 템플릿의 위쪽에 있는 목록에 지정됩니다. System.Collections.ObjectModel을 목록에 추가합니다.

var usings = new List<string>
{
    "System",
    "System.Collections.Generic",
    "System.Collections.ObjectModel"
};

리버스 엔지니어링 명령을 사용하여 변경 내용을 테스트합니다. 프로젝트 내의 템플릿은 명령에 의해 자동으로 사용됩니다.

dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Chinook" Microsoft.EntityFrameworkCore.SqlServer

이전에 명령을 실행한 경우 기존 파일을 덮어쓰는 옵션을 추가 --force 합니다.

모든 작업을 올바르게 한 경우 컬렉션 탐색 속성은 이제 .를 사용해야 ObservableCollection<T>합니다.

public virtual ICollection<Album> Albums { get; } = new ObservableCollection<Album>();

템플릿 업데이트

프로젝트에 기본 템플릿을 추가하면 해당 버전의 EF Core를 기반으로 해당 템플릿의 복사본이 만들어집니다. 버그가 수정되고 EF Core의 후속 버전에서 기능이 추가되면 템플릿이 만료될 수 있습니다. EF Core 템플릿에서 변경한 내용을 검토하고 사용자 지정된 템플릿에 병합해야 합니다.

EF Core 템플릿의 변경 내용을 검토하는 한 가지 방법은 git을 사용하여 버전 간에 비교하는 것입니다. 다음 명령은 EF Core 리포지토리를 복제하고 버전 7.0.0과 8.0.0 간에 이러한 파일의 diff를 생성합니다.

git clone --no-checkout https://github.com/dotnet/efcore.git
cd efcore
git diff v7.0.0 v8.0.0 -- src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.tt src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.tt

변경 내용을 검토하는 또 다른 방법은 NuGet에서 두 버전의 Microsoft.EntityFrameworkCore.Templates 를 다운로드하고, 콘텐츠를 추출하고(파일 확장명을 .zip변경할 수 있음) 해당 파일을 비교하는 것입니다.

새 프로젝트에 기본 템플릿을 추가하기 전에 최신 EF Core 템플릿 패키지로 업데이트해야 합니다.

dotnet new update

고급 사용

입력 모델 무시

ModelEntityType 매개 변수는 데이터베이스에 매핑할 수 있는 한 가지 방법을 나타냅니다. 모델의 일부를 무시하거나 변경하도록 선택할 수 있습니다. 예를 들어 제공하는 탐색 이름은 이상적이지 않을 수 있으며 코드를 스캐폴딩할 때 고유한 이름으로 바꿀 수 있습니다. 제약 조건 이름 및 인덱스 필터와 같은 다른 항목은 마이그레이션에서만 사용되며 스캐폴드된 코드와 함께 마이그레이션을 사용하지 않으려는 경우 모델에서 안전하게 생략할 수 있습니다. 마찬가지로 앱에서 시퀀스 또는 기본 제약 조건을 사용하지 않는 경우 생략할 수 있습니다.

이와 같이 고급 변경을 수행할 때 결과 모델이 데이터베이스와 호환되는지 확인합니다. 생성된 dbContext.Database.GenerateCreateScript() SQL을 검토하는 것이 이 유효성을 검사하는 좋은 방법입니다.

엔터티 구성 클래스

대형 모델의 경우 DbContext 클래스의 OnModelCreating 메서드가 관리되지 않게 커질 수 있습니다. 이 문제를 해결하는 한 가지 방법은 클래스를 사용하는 IEntityTypeConfiguration<T> 것입니다. 이러한 클래스에 대한 자세한 내용은 모델 만들기 및 구성 을 참조하세요.

이러한 클래스를 스캐폴드하려면 라는 EntityTypeConfiguration.t4세 번째 템플릿을 사용할 수 있습니다. 템플릿과 EntityType.t4 마찬가지로 모델의 각 엔터티 형식에 사용되고 템플릿 매개 변수를 EntityType 사용합니다.

다 대 다 관계에서 조인 테이블 생성

기본적으로 스캐폴딩 프로세스는 단순 다 대 다 관계에서 조인 테이블에 대한 엔터티를 생성하지 않습니다. 그러나 조인 테이블을 엔터티로 명시적으로 생성해야 하는 경우가 있습니다(예: 생성된 SQL 쿼리를 더 세부적으로 제어해야 하는 경우).

각 엔터티에 대한 스캐폴딩 동작은 EntityType.t4 템플릿 파일에 의해 제어됩니다. 이 파일 내에는 단순한 다대다 조인 테이블에 대한 엔터티 생성을 중단하는 조건이 있습니다. 이 동작을 재정의하고 조인 엔터티를 생성하려면 'EntityType.t4' 파일에서 이 조건을 주석 처리할 수 있습니다.

<#
    // Comment this condition
    if (EntityType.IsSimpleManyToManyJoinEntityType())
    {
        // Don't scaffold these
        return "";
    }
    .  .  .    
#>

다른 형식의 파일 스캐폴딩

EF Core에서 리버스 엔지니어링의 주요 목적은 DbContext 및 엔터티 형식을 스캐폴드하는 것입니다. 그러나 실제로 코드를 스캐폴드해야 하는 도구에는 아무것도 없습니다. 예를 들어 Mermaid를 사용하여 엔터티 관계 다이어그램을 스캐폴드할 수 있습니다.

<#@ output extension=".md" #>
<#@ assembly name="Microsoft.EntityFrameworkCore" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Relational" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Design" #>
<#@ parameter name="Model" type="Microsoft.EntityFrameworkCore.Metadata.IModel" #>
<#@ parameter name="Options" type="Microsoft.EntityFrameworkCore.Scaffolding.ModelCodeGenerationOptions" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="Microsoft.EntityFrameworkCore" #>
# <#= Options.ContextName #>

```mermaid
erDiagram
<#
    foreach (var entityType in Model.GetEntityTypes().Where(e => !e.IsSimpleManyToManyJoinEntityType()))
    {
#>
    <#= entityType.Name #> {
    }
<#
        foreach (var foreignKey in entityType.GetForeignKeys())
        {
#>
    <#= entityType.Name #> <#= foreignKey.IsUnique ? "|" : "}" #>o--<#= foreignKey.IsRequired ? "|" : "o" #>| <#= foreignKey.PrincipalEntityType.Name #> : "<#= foreignKey.GetConstraintName() #>"
<#
        }

        foreach (var skipNavigation in entityType.GetSkipNavigations().Where(n => n.IsLeftNavigation()))
        {
#>
    <#= entityType.Name #> }o--o{ <#= skipNavigation.TargetEntityType.Name #> : <#= skipNavigation.JoinEntityType.Name #>
<#
        }
    }
#>
```