Bekerja dengan Tipe Referensi Nullable
C# 8 memperkenalkan fitur baru yang disebut jenis referensi nullable (NRT), yang memungkinkan jenis referensi diannotasi, menunjukkan apakah itu valid untuk dikandung null
atau tidak. Jika Anda baru menggunakan fitur ini, disarankan agar Anda membiasakan diri dengannya dengan membaca dokumen C#. Jenis referensi nullable diaktifkan secara default dalam templat proyek baru, tetapi tetap dinonaktifkan dalam proyek yang ada kecuali secara eksplisit dipilih.
Halaman ini memperkenalkan dukungan EF Core untuk jenis referensi nullable, dan menjelaskan praktik terbaik untuk bekerja dengannya.
Properti yang diperlukan dan opsional
Dokumentasi utama tentang properti yang diperlukan dan opsional serta interaksinya dengan jenis referensi nullable adalah halaman Properti Wajib dan Opsional. Disarankan Anda memulai dengan membaca halaman tersebut terlebih dahulu.
Catatan
Berhati-hatilah saat mengaktifkan jenis referensi nullable pada proyek yang ada: properti jenis referensi yang sebelumnya dikonfigurasi sebagai opsional sekarang akan dikonfigurasi sesuai kebutuhan, kecuali jika secara eksplisit dianotasikan menjadi nullable. Saat mengelola skema database relasional, ini dapat menyebabkan migrasi dihasilkan yang mengubah nullability kolom database.
Properti dan inisialisasi yang tidak dapat diubah ke null
Ketika jenis referensi nullable diaktifkan, pengkompilasi C# memancarkan peringatan untuk properti yang tidak dapat diinisialisasi yang tidak dapat diubah ke null, karena ini akan berisi null
. Akibatnya, cara umum berikut untuk menulis jenis entitas tidak dapat digunakan:
public class Customer
{
public int Id { get; set; }
// Generates CS8618, uninitialized non-nullable property:
public string Name { get; set; }
}
Jika Anda menggunakan C# 11 atau lebih tinggi, anggota yang diperlukan memberikan solusi sempurna untuk masalah ini:
public required string Name { get; set; }
Pengkompilasi sekarang menjamin bahwa ketika kode Anda membuat instans Pelanggan, ia selalu menginisialisasi properti Namanya. Dan karena kolom database yang dipetakan ke properti tidak dapat diubah ke null, instans apa pun yang dimuat oleh EF juga selalu berisi Nama non-null.
Jika Anda menggunakan versi C#yang lebih lama, pengikatan Konstruktor adalah teknik alternatif untuk memastikan bahwa properti anda yang tidak dapat diubah ke null diinisialisasi:
public class CustomerWithConstructorBinding
{
public int Id { get; set; }
public string Name { get; set; }
public CustomerWithConstructorBinding(string name)
{
Name = name;
}
}
Sayangnya, dalam beberapa skenario pengikatan konstruktor bukanlah pilihan; properti navigasi, misalnya, tidak dapat diinisialisasi dengan cara ini. Dalam kasus tersebut, Anda cukup menginisialisasi properti ke null
dengan bantuan operator pengampunan null (tetapi lihat di bawah ini untuk detail selengkapnya):
public Product Product { get; set; } = null!;
Properti navigasi yang diperlukan
Properti navigasi yang diperlukan menyajikan kesulitan tambahan: meskipun dependen akan selalu ada untuk prinsipal tertentu, mungkin atau mungkin tidak dimuat oleh kueri tertentu, tergantung pada kebutuhan pada saat itu dalam program (lihat pola yang berbeda untuk memuat data). Pada saat yang sama, mungkin tidak diinginkan untuk membuat properti ini nullable, karena itu akan memaksa semua akses ke mereka untuk memeriksa null
, bahkan ketika navigasi diketahui dimuat dan oleh karena itu tidak dapat .null
Ini tidak selalu menjadi masalah! Selama dependen yang diperlukan dimuat dengan benar (misalnya melalui Include
), mengakses properti navigasinya dijamin untuk selalu mengembalikan non-null. Di sisi lain, aplikasi dapat memilih untuk memeriksa apakah hubungan dimuat dengan memeriksa apakah navigasi adalah null
. Dalam kasus seperti itu, masuk akal untuk membuat navigasi nullable. Ini berarti bahwa navigasi yang diperlukan dari dependen ke prinsipal:
- Harus tidak dapat diubah ke null jika dianggap sebagai kesalahan programmer untuk mengakses navigasi saat tidak dimuat.
- Harus nullable jika dapat diterima untuk kode aplikasi untuk memeriksa navigasi untuk menentukan apakah hubungan dimuat atau tidak.
Jika Anda menginginkan pendekatan yang lebih ketat, Anda dapat memiliki properti yang tidak dapat diubah ke null dengan bidang dukungan null:
private Address? _shippingAddress;
public Address ShippingAddress
{
set => _shippingAddress = value;
get => _shippingAddress
?? throw new InvalidOperationException("Uninitialized property: " + nameof(ShippingAddress));
}
Selama navigasi dimuat dengan benar, dependen akan dapat diakses melalui properti . Namun, jika properti diakses tanpa terlebih dahulu memuat entitas terkait dengan benar, dilemparkan InvalidOperationException
, karena kontrak API telah digunakan dengan tidak benar.
Catatan
Navigasi koleksi, yang berisi referensi ke beberapa entitas terkait, harus selalu tidak dapat diubah ke null. Koleksi kosong berarti bahwa tidak ada entitas terkait, tetapi daftar itu sendiri tidak boleh null
.
DbContext dan DbSet
Dengan EF, praktik umum untuk memiliki properti DbSet yang tidak diinisialisasi pada jenis konteks:
public class MyContext : DbContext
{
public DbSet<Customer> Customers { get; set;}
}
Meskipun ini umumnya menyebabkan peringatan kompilator, EF Core 7.0 ke atas menekan peringatan ini, karena EF secara otomatis menginisialisasi properti ini melalui pantulan.
Pada versi EF Core yang lebih lama, Anda dapat mengatasi masalah ini sebagai berikut:
public class MyContext : DbContext
{
public DbSet<Customer> Customers => Set<Customer>();
}
Strategi lain adalah menggunakan properti otomatis yang tidak dapat diubah ke null, tetapi untuk menginisialisasinya ke null
, menggunakan operator null-forgiving (!) untuk membungkam peringatan kompilator. Konstruktor dasar DbContext memastikan bahwa semua properti DbSet akan diinisialisasi, dan null tidak akan pernah diamati pada mereka.
Menavigasi dan menyertakan hubungan nullable
Saat berhadapan dengan hubungan opsional, dimungkinkan untuk menemukan peringatan kompilator di mana pengecualian referensi aktual null
tidak akan mungkin terjadi. Saat menerjemahkan dan menjalankan kueri LINQ Anda, EF Core menjamin bahwa jika entitas terkait opsional tidak ada, navigasi apa pun ke dalamnya hanya akan diabaikan, daripada melempar. Namun, kompilator tidak menyadari jaminan EF Core ini, dan menghasilkan peringatan seolah-olah kueri LINQ dijalankan dalam memori, dengan LINQ ke Objek. Akibatnya, perlu menggunakan operator pengampun null (!) untuk memberi tahu pengkompilasi bahwa nilai aktual null
tidak dimungkinkan:
var order = context.Orders
.Where(o => o.OptionalInfo!.SomeProperty == "foo")
.ToList();
Masalah serupa terjadi saat menyertakan beberapa tingkat hubungan di seluruh navigasi opsional:
var order = context.Orders
.Include(o => o.OptionalInfo!)
.ThenInclude(op => op.ExtraAdditionalInfo)
.Single();
Jika Anda sering melakukan ini, dan jenis entitas yang dimaksud sebagian besar (atau secara eksklusif) digunakan dalam kueri EF Core, pertimbangkan untuk membuat properti navigasi tidak dapat diubah ke null, dan untuk mengonfigurasinya sebagai opsional melalui API Fasih atau Anotasi Data. Ini akan menghapus semua peringatan kompilator sambil menjaga hubungan tetap opsional; namun, jika entitas Anda dilalui di luar EF Core, Anda dapat mengamati null
nilai meskipun properti dianotasi sebagai tidak dapat diubah ke null.
Batasan dalam versi yang lebih lama
Sebelum EF Core 6.0, batasan berikut diterapkan:
- Permukaan API publik tidak dianotasi untuk nullability (API publik "null-oblivious"), membuatnya kadang-kadang canggung untuk digunakan ketika fitur NRT diaktifkan. Ini terutama termasuk operator LINQ asinkron yang diekspos oleh EF Core, seperti FirstOrDefaultAsync. API publik sepenuhnya dianotasi untuk nullability yang dimulai dengan EF Core 6.0.
- Rekayasa terbalik tidak mendukung jenis referensi C# 8 nullable (NRTs): EF Core selalu menghasilkan kode C# yang mengasumsikan fitur tidak aktif. Misalnya, kolom teks nullable diperancah sebagai properti dengan jenis
string
, bukanstring?
, dengan Fluent API atau Anotasi Data yang digunakan untuk mengonfigurasi apakah properti diperlukan atau tidak. Jika menggunakan versi EF Core yang lebih lama, Anda masih bisa mengedit kode perancah dan menggantinya dengan anotasi nullability C#.