Jenis entitas dengan konstruktor

Dimungkinkan untuk menentukan konstruktor dengan parameter dan meminta EF Core memanggil konstruktor ini saat membuat instans entitas. Parameter konstruktor dapat dihubungkan ke properti yang dipetakan, atau ke berbagai layanan untuk memfasilitasi perilaku seperti lazy loading.

Nota

Saat ini, semua pengikatan konstruktor adalah berdasarkan konvensi. Konfigurasi konstruktor tertentu yang akan digunakan direncanakan untuk dirilis di masa mendatang.

Pengikatan ke properti yang dipetakan

Pertimbangkan model Blog/Posting yang khas:

public class Blog
{
    public int Id { get; set; }

    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }

    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

Ketika EF Core membuat instans jenis ini, seperti untuk hasil kueri, pertama-tama akan memanggil konstruktor tanpa parameter default lalu mengatur setiap properti ke nilai dari database. Namun, jika EF Core menemukan konstruktor berparameter dengan nama parameter dan jenis yang cocok dengan properti yang dipetakan, maka itu akan memanggil konstruktor berparameter dengan nilai untuk properti tersebut dan tidak akan mengatur setiap properti secara eksplisit. Contohnya:

public class Blog
{
    public Blog(int id, string name, string author)
    {
        Id = id;
        Name = name;
        Author = author;
    }

    public int Id { get; set; }

    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public Post(int id, string title, DateTime postedOn)
    {
        Id = id;
        Title = title;
        PostedOn = postedOn;
    }

    public int Id { get; set; }

    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

Yang perlu diperhatikan:

  • Tidak semua properti harus memiliki parameter konstruktor. Misalnya, properti Post.Content tidak diatur oleh parameter konstruktor apa pun, sehingga EF Core akan mengaturnya setelah memanggil konstruktor dengan cara normal.
  • Jenis dan nama parameter harus sesuai dengan jenis dan nama properti, kecuali properti dapat menggunakan Pascal casing sementara parameter menggunakan camel casing.
  • EF Core tidak dapat mengatur properti navigasi (seperti Blog atau Posting di atas) menggunakan konstruktor.
  • Konstruktor dapat bersifat publik, privat, atau memiliki aksesibilitas lain. Namun, proksi lazy loading mengharuskan konstruktor dapat diakses dari kelas proksi yang mewarisi. Biasanya ini berarti menjadikannya publik atau terlindungi.

Properti hanya bisa dibaca

Setelah properti diatur melalui konstruktor, masuk akal untuk menjadikan beberapa properti tersebut hanya-baca. EF Core mendukung hal ini, tetapi ada beberapa hal yang perlu diperhatikan:

  • Properti tanpa setter tidak dipetakan oleh konvensi. (Melakukannya cenderung memetakan properti yang tidak boleh dipetakan, seperti properti komputasi.)
  • Menggunakan nilai kunci yang dihasilkan secara otomatis memerlukan properti kunci yang dibaca-tulis, karena nilai kunci perlu diatur oleh generator kunci saat memasukkan entitas baru.

Cara mudah untuk menghindari hal-hal ini adalah dengan menggunakan setter privat. Contohnya:

public class Blog
{
    public Blog(int id, string name, string author)
    {
        Id = id;
        Name = name;
        Author = author;
    }

    public int Id { get; private set; }

    public string Name { get; private set; }
    public string Author { get; private set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public Post(int id, string title, DateTime postedOn)
    {
        Id = id;
        Title = title;
        PostedOn = postedOn;
    }

    public int Id { get; private set; }

    public string Title { get; private set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; private set; }

    public Blog Blog { get; set; }
}

EF Core melihat properti dengan pengatur privat sebagai dapat dibaca dan ditulis, yang berarti bahwa semua properti dipetakan seperti sebelumnya dan kunci masih dapat dihasilkan oleh penyimpanan.

Alternatif untuk menggunakan setter privat adalah membuat properti benar-benar hanya baca dan menambahkan pemetaan yang lebih eksplisit pada OnModelCreating. Demikian juga, beberapa properti dapat dihapus sepenuhnya dan diganti hanya dengan bidang. Misalnya, pertimbangkan jenis entitas ini:

public class Blog
{
    private int _id;

    public Blog(string name, string author)
    {
        Name = name;
        Author = author;
    }

    public string Name { get; }
    public string Author { get; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    private int _id;

    public Post(string title, DateTime postedOn)
    {
        Title = title;
        PostedOn = postedOn;
    }

    public string Title { get; }
    public string Content { get; set; }
    public DateTime PostedOn { get; }

    public Blog Blog { get; set; }
}

Dan konfigurasi ini di OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        b =>
        {
            b.HasKey("_id");
            b.Property(e => e.Author);
            b.Property(e => e.Name);
        });

    modelBuilder.Entity<Post>(
        b =>
        {
            b.HasKey("_id");
            b.Property(e => e.Title);
            b.Property(e => e.PostedOn);
        });
}

Hal yang perlu diperhatikan:

  • Kunci "properti" sekarang menjadi kolom. Ini bukan readonly kolom sehingga kunci yang dihasilkan oleh penyimpanan dapat digunakan.
  • Properti lainnya adalah properti baca-saja yang diatur hanya di konstruktor.
  • Jika nilai kunci utama hanya pernah ditetapkan oleh EF atau dibaca dari database, maka tidak perlu menyertakannya dalam konstruktor. Ini meninggalkan kunci "properti" sebagai bidang sederhana dan membuatnya jelas bahwa itu tidak boleh diatur secara eksplisit saat membuat blog atau posting baru.

Nota

Kode ini akan menghasilkan peringatan kompilator '169' yang menunjukkan bahwa bidang tidak pernah digunakan. Ini dapat diabaikan karena pada kenyataannya EF Core menggunakan bidang dengan cara ekstralinguistik.

Menyuntikkan layanan

EF Core juga dapat menyuntikkan "layanan" ke dalam konstruktor jenis entitas. Misalnya, berikut ini dapat disuntikkan:

  • DbContext - instans konteks saat ini, yang juga dapat ditetapkan sebagai jenis DbContext turunan Anda
  • ILazyLoader - layanan lazy-loading--lihat dokumentasi lazy-loading untuk detail selengkapnya
  • Action<object, string> - delegasi pemuatan malas--lihat dokumentasi pemuatan malas untuk detail selengkapnya
  • IEntityType - metadata EF Core yang terkait dengan jenis entitas ini

Nota

Saat ini, hanya layanan yang diketahui oleh EF Core yang dapat disuntikkan. Dukungan untuk menyuntikkan layanan aplikasi sedang dipertimbangkan untuk rilis mendatang.

Misalnya, DbContext yang disuntikkan dapat digunakan untuk secara selektif mengakses database untuk mendapatkan informasi tentang entitas terkait tanpa memuat semuanya. Dalam contoh di bawah ini digunakan untuk mendapatkan jumlah posting dalam blog tanpa memuat posting:

public class Blog
{
    public Blog()
    {
    }

    private Blog(BloggingContext context)
    {
        Context = context;
    }

    private BloggingContext Context { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; set; }

    public int PostsCount
        => Posts?.Count
           ?? Context?.Set<Post>().Count(p => Id == EF.Property<int?>(p, "BlogId"))
           ?? 0;
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

Beberapa hal yang perlu diperhatikan tentang hal ini:

  • Konstruktor bersifat privat, karena hanya dipanggil oleh EF Core, dan ada konstruktor publik lain untuk penggunaan umum.
  • Kode yang menggunakan layanan yang disuntikkan (yaitu, konteks) bertahan terhadap kemungkinan null untuk menangani kasus di mana EF Core tidak membuat instansinya.
  • Karena layanan disimpan dalam properti baca/tulis, layanan akan diatur ulang saat entitas dilampirkan ke instans konteks baru.

Peringatan

Menyuntikkan DbContext seperti ini sering dianggap sebagai anti-pola karena menggandeng jenis entitas Anda langsung ke EF Core. Pertimbangkan dengan cermat semua opsi sebelum menggunakan injeksi layanan seperti ini.