Bagikan melalui


Templat Rekayasa Terbalik Kustom

Nota

Fitur ini ditambahkan di EF Core 7.

Meskipun rekayasa terbalik, Entity Framework Core berusaha untuk menyusun kode tujuan umum yang baik dan dapat digunakan dalam berbagai jenis aplikasi dan menggunakan konvensi pengkodean umum untuk tampilan yang konsisten dan nuansa yang akrab. Namun, terkadang, kode yang lebih khusus dan gaya pengodean alternatif diinginkan. Artikel ini memperlihatkan cara mengkustomisasi kode perancah menggunakan templat teks T4.

Prasyarat

Artikel ini mengasumsikan Anda terbiasa dengan rekayasa terbalik di EF Core. Jika tidak, silakan tinjau artikel tersebut sebelum melanjutkan.

Menambahkan templat default

Langkah pertama untuk menyesuaikan kode yang dihasilkan adalah menambahkan templat default ke proyek Anda. Templat bawaan adalah templat yang digunakan secara internal oleh EF Core saat rekayasa mundur. Mereka menyediakan titik awal bagi Anda untuk mulai menyesuaikan kode yang sudah dipersiapkan.

Mulailah dengan menginstal paket templat EF Core untuk dotnet new:

dotnet new install Microsoft.EntityFrameworkCore.Templates

Sekarang Anda dapat menambahkan templat default ke proyek Anda. Lakukan ini dengan menjalankan perintah berikut dari direktori proyek Anda.

dotnet new ef-templates

Perintah ini menambahkan file berikut ke proyek Anda.

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

Templat DbContext.t4 digunakan untuk membuat perancah kelas DbContext untuk database, dan templat EntityType.t4 digunakan untuk membuat perancah kelas jenis entitas untuk setiap tabel dan tampilan dalam database.

Petunjuk / Saran

Ekstensi .t4 digunakan (bukan .tt) untuk mencegah Visual Studio mengubah templat. Templat akan diubah oleh EF Core sebagai gantinya.

Pengantar T4

Mari kita buka templat DbContext.t4 dan periksa kontennya. File ini adalah templat teks T4. T4 adalah bahasa untuk menghasilkan teks menggunakan .NET. Kode berikut hanya untuk tujuan ilustrasi; ini tidak mewakili isi lengkap file.

Penting

Templat teks T4--terutama yang menghasilkan kode--bisa sulit dibaca tanpa penyorotan sintaks. Jika perlu, cari ekstensi ke editor kode Anda yang mengaktifkan penyorotan sintaks 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 #>;

Beberapa baris pertama yang dimulai dengan <#@ disebut direktif. Mereka memengaruhi bagaimana templat diubah bentuknya. Tabel berikut ini secara singkat menjelaskan setiap jenis direktif yang digunakan.

Direktif Deskripsi
template Menentukan hostSpecific="true" yang memungkinkan penggunaan properti Host di dalam templat untuk mengakses layanan EF Core.
assembly Menambahkan referensi rakitan yang diperlukan untuk mengkompilasi templat.
parameter Mendeklarasikan parameter yang akan diteruskan oleh EF Core saat mengubah templat.
import Seperti menggunakan direktif C#, memasukkan namespace ke dalam cakupan untuk kode template.

Setelah arahan, bagian berikutnya dari DbContext.t4 disebut blok kontrol. Blok kontrol standar dimulai dengan <# dan diakhir dengan #>. Kode di dalamnya akan dijalankan saat mengubah templat. Untuk daftar properti dan metode yang tersedia di dalam blok kontrol, lihat kelas TextTransformation.

Apa pun di luar blok kontrol akan disalin langsung ke output templat.

Blok kontrol ekspresi dimulai dengan <#=. Kode di dalamnya akan dievaluasi dan hasilnya akan ditambahkan ke output templat. Ini mirip dengan argumen string terinterpolasi C#.

Untuk penjelasan yang lebih rinci dan lengkap tentang sintaks T4, lihat Menulis Templat Teks T4.

Mengkustomisasi jenis entitas

Mari kita telusuri bagaimana rasanya mengkustomisasi templat. Secara default, EF Core menghasilkan kode berikut untuk properti navigasi koleksi.

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

Menggunakan List<T> adalah default yang baik untuk sebagian besar aplikasi. Namun, jika Anda menggunakan kerangka kerja berbasis XAML seperti WPF, WinUI, atau .NET MAUI, Anda sering ingin menggunakan ObservableCollection<T> sebagai gantinya untuk mengaktifkan pengikatan data.

Buka templat EntityType.t4 dan temukan di mana templat tersebut menghasilkan List<T>. Sepertinya ini:

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

Ganti Daftar dengan ObservableCollection.

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

Kita juga harus menambahkan arahan using ke dalam kode perancah. Penggunaan ditentukan dalam daftar di dekat bagian atas templat. Tambahkan System.Collections.ObjectModel ke daftar.

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

Uji perubahan dengan menggunakan perintah rekayasa terbalik. Templat di dalam proyek Anda digunakan secara otomatis oleh perintah.

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

Jika Anda telah menjalankan perintah sebelumnya, tambahkan opsi --force tersebut untuk menimpa file-file yang sudah ada.

Jika Anda melakukan semua langkah dengan benar, properti navigasi koleksi sekarang harus menggunakan ObservableCollection<T>.

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

Memperbarui templat

Saat Anda menambahkan templat default ke proyek Anda, templat tersebut akan membuat salinannya berdasarkan versi EF Core tersebut. Karena bug diperbaiki dan fitur ditambahkan dalam versi EF Core berikutnya, templat Anda mungkin kedaluarsa. Anda harus meninjau perubahan yang dibuat dalam templat EF Core dan menggabungkannya ke dalam templat yang disesuaikan.

Salah satu cara untuk meninjau perubahan yang dilakukan pada templat EF Core adalah dengan menggunakan git untuk membandingkannya di antara versi. Perintah berikut akan mengkloning repositori EF Core dan menghasilkan perbedaan file-file ini antara versi 7.0.0 dan 8.0.0.

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

Cara lain untuk meninjau perubahan adalah dengan mengunduh dua versi Microsoft.EntityFrameworkCore.Templates dari NuGet, mengekstrak kontennya (Anda dapat mengubah ekstensi file menjadi .zip), dan membandingkan file tersebut.

Sebelum menambahkan templat default ke proyek baru, ingatlah untuk memperbarui ke paket templat EF Core terbaru.

dotnet new update

Penggunaan tingkat lanjut

Mengabaikan model input

Parameter Model dan EntityType mewakili salah satu cara pemetaan yang mungkin ke database. Anda dapat memilih untuk mengabaikan atau mengubah bagian model. Misalnya, nama navigasi yang kami berikan mungkin tidak ideal, dan Anda dapat menggantinya dengan nama Anda sendiri saat membuat perancah kode. Hal lain seperti nama batasan dan filter indeks hanya digunakan oleh Migrasi dan dapat dihilangkan dengan aman dari model jika Anda tidak berniat menggunakan Migrasi dengan kode perancah. Demikian juga, Anda mungkin ingin menghilangkan urutan atau batasan default jika tidak digunakan oleh aplikasi Anda.

Saat membuat perubahan tingkat lanjut seperti ini, pastikan model yang dihasilkan tetap kompatibel dengan database. Meninjau SQL yang dihasilkan oleh dbContext.Database.GenerateCreateScript() adalah cara yang baik untuk memvalidasi ini.

Kelas konfigurasi entitas

Untuk model yang lebih besar, metode OnModelCreating dari kelas DbContext dapat menjadi sulit untuk dikelola karena ukurannya. Salah satu cara untuk mengatasinya adalah dengan menggunakan kelas IEntityTypeConfiguration<T>. Lihat Membuat dan mengonfigurasi model untuk informasi selengkapnya tentang kelas-kelas ini.

Untuk menyusun kelas ini, Anda dapat menggunakan templat ketiga yang disebut EntityTypeConfiguration.t4. Seperti templat EntityType.t4, templat akan digunakan untuk setiap jenis entitas dalam model dan menggunakan parameter templat EntityType.

Hasilkan Tabel Penghubung pada Hubungan Banyak ke Banyak

Secara bawaan, proses perancah pemrograman tidak menghasilkan entitas untuk tabel gabungan dalam hubungan banyak-ke-banyak sederhana. Namun, ada kasus di mana secara eksplisit menghasilkan tabel gabungan sebagai entitas mungkin diperlukan (misalnya, ketika kontrol yang lebih halus atas kueri SQL yang dihasilkan diperlukan).

Perilaku perancah untuk setiap entitas dikontrol oleh file templat EntityType.t4. Dalam file ini, terdapat kondisi yang menghentikan pembuatan entitas untuk tabel penghubung banyak-ke-banyak yang sederhana. Untuk mengambil alih perilaku ini dan menghasilkan entitas gabungan, Anda dapat mengomentari kondisi ini dalam file 'EntityType.t4'.

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

Perancah jenis file lain

Tujuan utama rekayasa terbalik di EF Core adalah untuk menyusun DbContext dan jenis entitas. Namun, tidak ada dalam alat yang mengharuskan Anda untuk benar-benar membangun kerangka kode. Misalnya, Anda dapat menyusun diagram hubungan entitas menggunakan 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 #>
<#
        }
    }
#>
```