Bagikan melalui


Konvensi Pertama Kode Kustom

Catatan

Hanya EF6 dan seterusnya - Fitur, API, dll. yang dibahas di halaman ini dimasukkan dalam Entity Framework 6. Jika Anda menggunakan versi yang lebih lama, beberapa atau semua informasi tidak berlaku.

Saat menggunakan Kode Pertama, model Anda dihitung dari kelas Anda menggunakan serangkaian konvensi. Konvensi Pertama Kode default menentukan hal-hal seperti properti mana yang menjadi kunci utama entitas, nama tabel yang dipetakan entitas, dan presisi dan skala apa yang dimiliki kolom desimal secara default.

Terkadang konvensi default ini tidak ideal untuk model Anda, dan Anda harus mengatasinya dengan mengonfigurasi banyak entitas individual menggunakan Anotasi Data atau API Fasih. Konvensi Pertama Kode Kustom memungkinkan Anda menentukan konvensi Anda sendiri yang menyediakan default konfigurasi untuk model Anda. Dalam panduan ini, kita akan menjelajahi berbagai jenis konvensi kustom dan cara membuat masing-masing konvensi.

Konvensi Berbasis Model

Halaman ini mencakup API DbModelBuilder untuk konvensi kustom. API ini harus cukup untuk menulis sebagian besar konvensi kustom. Namun, ada juga kemampuan untuk menulis konvensi berbasis model - konvensi yang memanipulasi model akhir setelah dibuat - untuk menangani skenario tingkat lanjut. Untuk informasi selengkapnya, lihat Konvensi Berbasis Model.

 

Model Kami

Mari kita mulai dengan menentukan model sederhana yang dapat kita gunakan dengan konvensi kita. Tambahkan kelas berikut ke proyek Anda.

    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;

    public class ProductContext : DbContext
    {
        static ProductContext()
        {
            Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ProductContext>());
        }

        public DbSet<Product> Products { get; set; }
    }

    public class Product
    {
        public int Key { get; set; }
        public string Name { get; set; }
        public decimal? Price { get; set; }
        public DateTime? ReleaseDate { get; set; }
        public ProductCategory Category { get; set; }
    }

    public class ProductCategory
    {
        public int Key { get; set; }
        public string Name { get; set; }
        public List<Product> Products { get; set; }
    }

 

Memperkenalkan Konvensi Kustom

Mari kita tulis konvensi yang mengonfigurasi properti apa pun bernama Key untuk menjadi kunci utama untuk jenis entitasnya.

Konvensi diaktifkan pada pembuat model, yang dapat diakses dengan mengambil alih OnModelCreating dalam konteks. Perbarui kelas ProductContext sebagai berikut:

    public class ProductContext : DbContext
    {
        static ProductContext()
        {
            Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ProductContext>());
        }

        public DbSet<Product> Products { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Properties()
                        .Where(p => p.Name == "Key")
                        .Configure(p => p.IsKey());
        }
    }

Sekarang, properti apa pun dalam model kami bernama Key akan dikonfigurasi sebagai kunci utama dari entitas apa pun yang menjadi bagiannya.

Kita juga dapat membuat konvensi kita lebih spesifik dengan memfilter jenis properti yang akan kita konfigurasi:

    modelBuilder.Properties<int>()
                .Where(p => p.Name == "Key")
                .Configure(p => p.IsKey());

Ini akan mengonfigurasi semua properti yang disebut Kunci untuk menjadi kunci utama entitas mereka, tetapi hanya jika mereka adalah bilangan bulat.

Fitur menarik dari metode IsKey adalah bahwa itu adalah aditif. Yang berarti bahwa jika Anda memanggil IsKey pada beberapa properti dan semuanya akan menjadi bagian dari kunci komposit. Satu peringatan untuk ini adalah bahwa ketika Anda menentukan beberapa properti untuk kunci, Anda juga harus menentukan urutan untuk properti tersebut. Anda dapat melakukan ini dengan memanggil metode HasColumnOrder seperti di bawah ini:

    modelBuilder.Properties<int>()
                .Where(x => x.Name == "Key")
                .Configure(x => x.IsKey().HasColumnOrder(1));

    modelBuilder.Properties()
                .Where(x => x.Name == "Name")
                .Configure(x => x.IsKey().HasColumnOrder(2));

Kode ini akan mengonfigurasi jenis dalam model kami untuk memiliki kunci komposit yang terdiri dari kolom Kunci int dan kolom Nama string. Jika kita melihat model di perancang, model akan terlihat seperti ini:

composite Key

Contoh lain dari konvensi properti adalah mengonfigurasi semua properti DateTime dalam model saya untuk memetakan ke jenis datetime2 di SQL Server alih-alih tanggalwaktu. Anda dapat mencapai ini dengan hal berikut:

    modelBuilder.Properties<DateTime>()
                .Configure(c => c.HasColumnType("datetime2"));

 

Kelas Konvensi

Cara lain untuk menentukan konvensi adalah dengan menggunakan Kelas Konvensi untuk merangkum konvensi Anda. Saat menggunakan Kelas Konvensi, Anda membuat jenis yang mewarisi dari kelas Konvensi di namespace System.Data.Entity.ModelConfiguration.Conventions.

Kita dapat membuat Kelas Konvensi dengan konvensi datetime2 yang kami tunjukkan sebelumnya dengan melakukan hal berikut:

    public class DateTime2Convention : Convention
    {
        public DateTime2Convention()
        {
            this.Properties<DateTime>()
                .Configure(c => c.HasColumnType("datetime2"));        
        }
    }

Untuk memberi tahu EF untuk menggunakan konvensi ini, Anda menambahkannya ke koleksi Konvensi di OnModelCreating, yang jika Anda telah mengikuti panduan akan terlihat seperti ini:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Properties<int>()
                    .Where(p => p.Name.EndsWith("Key"))
                    .Configure(p => p.IsKey());

        modelBuilder.Conventions.Add(new DateTime2Convention());
    }

Seperti yang Anda lihat, kami menambahkan instans konvensi kami ke koleksi konvensi. Mewarisi dari Konvensi menyediakan cara yang mudah untuk mengelompokkan dan berbagi konvensi di seluruh tim atau proyek. Anda dapat, misalnya, memiliki pustaka kelas dengan serangkaian konvensi umum yang digunakan semua proyek organisasi Anda.

 

Atribut Kustom

Penggunaan konvensi hebat lainnya adalah mengaktifkan atribut baru untuk digunakan saat mengonfigurasi model. Untuk mengilustrasikan hal ini, mari kita buat atribut yang dapat kita gunakan untuk menandai properti String sebagai non-Unicode.

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class NonUnicode : Attribute
    {
    }

Sekarang, mari kita buat konvensi untuk menerapkan atribut ini ke model kita:

    modelBuilder.Properties()
                .Where(x => x.GetCustomAttributes(false).OfType<NonUnicode>().Any())
                .Configure(c => c.IsUnicode(false));

Dengan konvensi ini kita dapat menambahkan atribut NonUnicode ke salah satu properti string kita, yang berarti kolom dalam database akan disimpan sebagai varchar alih-alih nvarchar.

Satu hal yang perlu diperhatikan tentang konvensi ini adalah bahwa jika Anda menempatkan atribut NonUnicode pada apa pun selain properti string maka itu akan melemparkan pengecualian. Ini dilakukan karena Anda tidak dapat mengonfigurasi IsUnicode pada jenis apa pun selain string. Jika ini terjadi, maka Anda dapat membuat konvensi Anda lebih spesifik, sehingga memfilter apa pun yang bukan string.

Sementara konvensi di atas berfungsi untuk menentukan atribut kustom, ada API lain yang bisa jauh lebih mudah digunakan, terutama ketika Anda ingin menggunakan properti dari kelas atribut.

Untuk contoh ini, kita akan memperbarui atribut dan mengubahnya menjadi atribut IsUnicode, sehingga terlihat seperti ini:

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    internal class IsUnicode : Attribute
    {
        public bool Unicode { get; set; }

        public IsUnicode(bool isUnicode)
        {
            Unicode = isUnicode;
        }
    }

Setelah kita memiliki ini, kita dapat mengatur bool pada atribut kita untuk memberi tahu konvensi apakah properti harus Unicode atau tidak. Kita dapat melakukan ini dalam konvensi yang telah kita miliki dengan mengakses ClrProperty dari kelas konfigurasi seperti ini:

    modelBuilder.Properties()
                .Where(x => x.GetCustomAttributes(false).OfType<IsUnicode>().Any())
                .Configure(c => c.IsUnicode(c.ClrPropertyInfo.GetCustomAttribute<IsUnicode>().Unicode));

Ini cukup mudah, tetapi ada cara yang lebih tepat untuk mencapai ini dengan menggunakan metode Memiliki API konvensi. Metode Having memiliki parameter jenis Func<PropertyInfo, T> yang menerima PropertyInfo sama dengan metode Where, tetapi diharapkan untuk mengembalikan objek. Jika objek yang dikembalikan null maka properti tidak akan dikonfigurasi, yang berarti Anda dapat memfilter properti dengannya seperti Di mana, tetapi berbeda karena juga akan menangkap objek yang dikembalikan dan meneruskannya ke metode Konfigurasikan. Ini berfungsi seperti berikut ini:

    modelBuilder.Properties()
                .Having(x => x.GetCustomAttributes(false).OfType<IsUnicode>().FirstOrDefault())
                .Configure((config, att) => config.IsUnicode(att.Unicode));

Atribut kustom bukan satu-satunya alasan untuk menggunakan metode Having, ini berguna di mana saja Anda perlu beralasan tentang sesuatu yang Anda filter saat mengonfigurasi jenis atau properti Anda.

 

Mengonfigurasi Jenis

Sejauh ini semua konvensi kami telah untuk properti, tetapi ada area lain dari API konvensi untuk mengonfigurasi jenis dalam model Anda. Pengalaman ini mirip dengan konvensi yang telah kita lihat sejauh ini, tetapi opsi di dalam konfigurasi akan berada di entitas alih-alih tingkat properti.

Salah satu hal yang dapat sangat berguna untuk konvensi tingkat Jenis adalah mengubah konvensi penamaan tabel, baik untuk memetakan ke skema yang ada yang berbeda dari default EF atau untuk membuat database baru dengan konvensi penamaan yang berbeda. Untuk melakukan ini, pertama-tama kita memerlukan metode yang dapat menerima TypeInfo untuk jenis dalam model kita dan mengembalikan nama tabel untuk jenis tersebut:

    private string GetTableName(Type type)
    {
        var result = Regex.Replace(type.Name, ".[A-Z]", m => m.Value[0] + "_" + m.Value[1]);

        return result.ToLower();
    }

Metode ini mengambil jenis dan mengembalikan string yang menggunakan huruf kecil dengan garis bawah alih-alih CamelCase. Dalam model kami, ini berarti bahwa kelas ProductCategory akan dipetakan ke tabel yang disebut product_category alih-alih ProductCategories.

Setelah kita memiliki metode itu, kita dapat menyebutnya dalam konvensi seperti ini:

    modelBuilder.Types()
                .Configure(c => c.ToTable(GetTableName(c.ClrType)));

Konvensi ini mengonfigurasi setiap jenis dalam model kami untuk memetakan ke nama tabel yang dikembalikan dari metode GetTableName kami. Konvensi ini setara dengan memanggil metode ToTable untuk setiap entitas dalam model menggunakan FLUENT API.

Satu hal yang perlu diperhatikan tentang ini adalah bahwa ketika Anda memanggil ToTable EF akan mengambil string yang Anda berikan sebagai nama tabel yang tepat, tanpa salah satu pluralisasi yang biasanya dilakukan saat menentukan nama tabel. Inilah sebabnya mengapa nama tabel dari konvensi kami product_category alih-alih product_categories. Kita dapat menyelesaikannya dalam konvensi kita dengan melakukan panggilan ke layanan pluralisasi sendiri.

Dalam kode berikut, kami akan menggunakan fitur Resolusi Dependensi yang ditambahkan di EF6 untuk mengambil layanan pluralisasi yang akan digunakan EF dan mem-pluralisasi nama tabel kami.

    private string GetTableName(Type type)
    {
        var pluralizationService = DbConfiguration.DependencyResolver.GetService<IPluralizationService>();

        var result = pluralizationService.Pluralize(type.Name);

        result = Regex.Replace(result, ".[A-Z]", m => m.Value[0] + "_" + m.Value[1]);

        return result.ToLower();
    }

Catatan

Versi generik GetService adalah metode ekstensi dalam namespace Layanan System.Data.Entity.Infrastructure.DependencyResolution, Anda harus menambahkan pernyataan penggunaan ke konteks Anda untuk menggunakannya.

ToTable dan Warisan

Aspek penting lain dari ToTable adalah bahwa jika Anda secara eksplisit memetakan jenis ke tabel tertentu, maka Anda dapat mengubah strategi pemetaan yang akan digunakan EF. Jika Anda memanggil ToTable untuk setiap jenis dalam hierarki pewarisan, meneruskan nama jenis sebagai nama tabel seperti yang kami lakukan di atas, maka Anda akan mengubah strategi pemetaan Table-Per-Hierarchy (TPH) default menjadi Table-Per-Type (TPT). Cara terbaik untuk menggambarkan ini adalah dengan contoh konkret:

    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class Manager : Employee
    {
        public string SectionManaged { get; set; }
    }

Secara default karyawan dan manajer dipetakan ke tabel yang sama (Karyawan) dalam database. Tabel akan berisi karyawan dan manajer dengan kolom diskriminator yang akan memberi tahu Anda jenis instans apa yang disimpan di setiap baris. Ini adalah pemetaan TPH karena ada satu tabel untuk hierarki. Namun, jika Anda memanggil ToTable pada kedua kelas, maka setiap jenis akan dipetakan ke tabelnya sendiri, juga dikenal sebagai TPT karena setiap jenis memiliki tabelnya sendiri.

    modelBuilder.Types()
                .Configure(c=>c.ToTable(c.ClrType.Name));

Kode di atas akan memetakan ke struktur tabel yang terlihat seperti berikut ini:

tpt Example

Anda dapat menghindari hal ini, dan mempertahankan pemetaan TPH default, dalam beberapa cara:

  1. Panggil ToTable dengan nama tabel yang sama untuk setiap jenis dalam hierarki.
  2. Panggil ToTable hanya pada kelas dasar hierarki, dalam contoh kami yang akan menjadi karyawan.

 

Urutan Eksekusi

Konvensi beroperasi dengan cara menang terakhir, sama dengan API Fluent. Artinya, jika Anda menulis dua konvensi yang mengonfigurasi opsi yang sama dari properti yang sama, maka yang terakhir untuk mengeksekusi menang. Sebagai contoh, dalam kode di bawah panjang maksimum semua string diatur ke 500 tetapi kami kemudian mengonfigurasi semua properti yang disebut Nama dalam model untuk memiliki panjang maksimum 250.

    modelBuilder.Properties<string>()
                .Configure(c => c.HasMaxLength(500));

    modelBuilder.Properties<string>()
                .Where(x => x.Name == "Name")
                .Configure(c => c.HasMaxLength(250));

Karena konvensi untuk mengatur panjang maksimum ke 250 adalah setelah yang mengatur semua string ke 500, semua properti yang disebut Nama dalam model kami akan memiliki MaxLength 250 sementara string lainnya, seperti deskripsi, akan menjadi 500. Menggunakan konvensi dengan cara ini berarti Anda dapat menyediakan konvensi umum untuk jenis atau properti dalam model Anda dan kemudian menimpanya untuk subset yang berbeda.

API Fasih dan Anotasi Data juga dapat digunakan untuk mengambil alih konvensi dalam kasus tertentu. Dalam contoh kami di atas jika kami telah menggunakan API Fasih untuk mengatur panjang maksimum properti maka kita bisa meletakkannya sebelum atau sesudah konvensi, karena API Fasih yang lebih spesifik akan menang atas Konvensi Konfigurasi yang lebih umum.

 

Konvensi Bawaan

Karena konvensi kustom dapat dipengaruhi oleh konvensi Kode Pertama default, dapat berguna untuk menambahkan konvensi yang akan dijalankan sebelum atau sesudah konvensi lain. Untuk melakukan ini, Anda dapat menggunakan metode AddBefore dan AddAfter dari koleksi Konvensi pada DbContext turunan Anda. Kode berikut akan menambahkan kelas konvensi yang kami buat sebelumnya sehingga akan berjalan sebelum konvensi penemuan kunci bawaan.

    modelBuilder.Conventions.AddBefore<IdKeyDiscoveryConvention>(new DateTime2Convention());

Ini akan menjadi penggunaan paling banyak saat menambahkan konvensi yang perlu dijalankan sebelum atau sesudah konvensi bawaan, daftar konvensi bawaan dapat ditemukan di sini: System.Data.Entity.ModelConfiguration.Conventions Namespace.

Anda juga dapat menghapus konvensi yang tidak ingin Diterapkan ke model Anda. Untuk menghapus konvensi, gunakan metode Hapus. Berikut adalah contoh menghapus PluralizingTableNameConvention.

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }