Menangani Hubungan Entitas
Bagian ini menjelaskan beberapa detail tentang bagaimana EF memuat entitas terkait, dan cara menangani properti navigasi melingkar di kelas model Anda. (Bagian ini memberikan pengetahuan latar belakang, dan tidak diperlukan untuk menyelesaikan tutorial. Jika mau, lewati ke Bagian 5..)
Pemuatan Bersemangat versus Pemuatan Malas
Saat menggunakan EF dengan database relasional, penting untuk memahami bagaimana EF memuat data terkait.
Ini juga berguna untuk melihat kueri SQL yang dihasilkan EF. Untuk melacak SQL, tambahkan baris kode berikut ke BookServiceContext
konstruktor:
public BookServiceContext() : base("name=BookServiceContext")
{
// New code:
this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
}
Jika Anda mengirim permintaan GET ke /api/books, permintaan tersebut mengembalikan JSON seperti berikut:
[
{
"BookId": 1,
"Title": "Pride and Prejudice",
"Year": 1813,
"Price": 9.99,
"Genre": "Comedy of manners",
"AuthorId": 1,
"Author": null
},
...
Anda dapat melihat bahwa properti Pembuat null, meskipun buku berisi AuthorId yang valid. Itu karena EF tidak memuat entitas Penulis terkait. Log jejak kueri SQL mengonfirmasi hal ini:
SELECT
[Extent1].[BookId] AS [BookId],
[Extent1].[Title] AS [Title],
[Extent1].[Year] AS [Year],
[Extent1].[Price] AS [Price],
[Extent1].[Genre] AS [Genre],
[Extent1].[AuthorId] AS [AuthorId]
FROM [dbo].[Books] AS [Extent1]
Pernyataan SELECT mengambil dari tabel Buku, dan tidak mereferensikan tabel Penulis.
Sebagai referensi, berikut adalah metode di BooksController
kelas yang mengembalikan daftar buku.
public IQueryable<Book> GetBooks()
{
return db.Books;
}
Mari kita lihat bagaimana kita dapat mengembalikan Pembuat sebagai bagian dari data JSON. Ada tiga cara untuk memuat data terkait dalam Kerangka Kerja Entitas: pemuatan bersemangat, pemuatan malas, dan pemuatan eksplisit. Ada trade-off dengan setiap teknik, jadi penting untuk memahami cara kerjanya.
Pemuatan Bersemangat
Dengan pemuatan yang bersemangat, EF memuat entitas terkait sebagai bagian dari kueri database awal. Untuk melakukan pemuatan yang bersemangat, gunakan metode ekstensi System.Data.Entity.Include .
public IQueryable<Book> GetBooks()
{
return db.Books
// new code:
.Include(b => b.Author);
}
Ini memberi tahu EF untuk menyertakan data Penulis dalam kueri. Jika Anda membuat perubahan ini dan menjalankan aplikasi, sekarang data JSON terlihat seperti ini:
[
{
"BookId": 1,
"Title": "Pride and Prejudice",
"Year": 1813,
"Price": 9.99,
"Genre": "Comedy of manners",
"AuthorId": 1,
"Author": {
"AuthorId": 1,
"Name": "Jane Austen"
}
},
...
Log jejak menunjukkan bahwa EF melakukan gabungan pada tabel Buku dan Penulis.
SELECT
[Extent1].[BookId] AS [BookId],
[Extent1].[Title] AS [Title],
[Extent1].[Year] AS [Year],
[Extent1].[Price] AS [Price],
[Extent1].[Genre] AS [Genre],
[Extent1].[AuthorId] AS [AuthorId],
[Extent2].[AuthorId] AS [AuthorId1],
[Extent2].[Name] AS [Name]
FROM [dbo].[Books] AS [Extent1]
INNER JOIN [dbo].[Authors] AS [Extent2] ON [Extent1].[AuthorId] = [Extent2].[AuthorId]
Pemuatan Malas
Dengan pemuatan malas, EF secara otomatis memuat entitas terkait ketika properti navigasi untuk entitas tersebut didereferensikan. Untuk mengaktifkan pemuatan malas, buat properti navigasi virtual. Misalnya, di kelas Buku:
public class Book
{
// (Other properties)
// Virtual navigation property
public virtual Author Author { get; set; }
}
Sekarang pertimbangkan kode berikut:
var books = db.Books.ToList(); // Does not load authors
var author = books[0].Author; // Loads the author for books[0]
Saat pemuatan malas diaktifkan, mengakses Author
properti menyebabkan books[0]
EF mengkueri database untuk penulis.
Pemuatan malas memerlukan beberapa perjalanan database, karena EF mengirim kueri setiap kali mengambil entitas terkait. Umumnya, Anda ingin pemuatan malas dinonaktifkan untuk objek yang Anda serialisasikan. Serializer harus membaca semua properti pada model, yang memicu pemuatan entitas terkait. Misalnya, berikut adalah kueri SQL saat EF membuat serialisasi daftar buku dengan pemuatan malas diaktifkan. Anda dapat melihat bahwa EF membuat tiga kueri terpisah untuk tiga penulis.
SELECT
[Extent1].[BookId] AS [BookId],
[Extent1].[Title] AS [Title],
[Extent1].[Year] AS [Year],
[Extent1].[Price] AS [Price],
[Extent1].[Genre] AS [Genre],
[Extent1].[AuthorId] AS [AuthorId]
FROM [dbo].[Books] AS [Extent1]
SELECT
[Extent1].[AuthorId] AS [AuthorId],
[Extent1].[Name] AS [Name]
FROM [dbo].[Authors] AS [Extent1]
WHERE [Extent1].[AuthorId] = @EntityKeyValue1
SELECT
[Extent1].[AuthorId] AS [AuthorId],
[Extent1].[Name] AS [Name]
FROM [dbo].[Authors] AS [Extent1]
WHERE [Extent1].[AuthorId] = @EntityKeyValue1
SELECT
[Extent1].[AuthorId] AS [AuthorId],
[Extent1].[Name] AS [Name]
FROM [dbo].[Authors] AS [Extent1]
WHERE [Extent1].[AuthorId] = @EntityKeyValue1
Masih ada kalanya Anda mungkin ingin menggunakan pemuatan malas. Pemuatan yang bersemangat dapat menyebabkan EF menghasilkan gabungan yang sangat kompleks. Atau Anda mungkin memerlukan entitas terkait untuk sebagian kecil data, dan pemuatan malas akan lebih efisien.
Salah satu cara untuk menghindari masalah serialisasi adalah dengan membuat serialisasi objek transfer data (DTO) alih-alih objek entitas. Saya akan menunjukkan pendekatan ini nanti di artikel.
Pemuatan Eksplisit
Pemuatan eksplisit mirip dengan pemuatan malas, kecuali bahwa Anda secara eksplisit mendapatkan data terkait dalam kode; itu tidak terjadi secara otomatis saat Anda mengakses properti navigasi. Pemuatan eksplisit memberi Anda lebih banyak kontrol atas kapan harus memuat data terkait, tetapi memerlukan kode tambahan. Untuk informasi selengkapnya tentang pemuatan eksplisit, lihat Memuat Entitas Terkait.
Properti Navigasi dan Referensi Melingkar
Ketika saya mendefinisikan model Buku dan Penulis, saya menentukan properti navigasi pada Book
kelas untuk hubungan Book-Author, tetapi saya tidak menentukan properti navigasi ke arah lain.
Apa yang terjadi jika Anda menambahkan properti navigasi yang sesuai ke Author
kelas ?
public class Author
{
public int AuthorId { get; set; }
[Required]
public string Name { get; set; }
public ICollection<Book> Books { get; set; }
}
Sayangnya, ini menciptakan masalah ketika Anda menserialisasikan model. Jika Anda memuat data terkait, data tersebut akan membuat grafik objek melingkar.
Ketika formatter JSON atau XML mencoba membuat serialisasi grafik, itu akan melemparkan pengecualian. Kedua pemformat melempar pesan pengecualian yang berbeda. Berikut adalah contoh untuk pemformat JSON:
{
"Message": "An error has occurred.",
"ExceptionMessage": "The 'ObjectContent`1' type failed to serialize the response body for content type
'application/json; charset=utf-8'.",
"ExceptionType": "System.InvalidOperationException",
"StackTrace": null,
"InnerException": {
"Message": "An error has occurred.",
"ExceptionMessage": "Self referencing loop detected with type 'BookService.Models.Book'.
Path '[0].Author.Books'.",
"ExceptionType": "Newtonsoft.Json.JsonSerializationException",
"StackTrace": "..."
}
}
Berikut adalah pemformat XML:
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>The 'ObjectContent`1' type failed to serialize the response body for content type
'application/xml; charset=utf-8'.</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace />
<InnerException>
<Message>An error has occurred.</Message>
<ExceptionMessage>Object graph for type 'BookService.Models.Author' contains cycles and cannot be
serialized if reference tracking is disabled.</ExceptionMessage>
<ExceptionType>System.Runtime.Serialization.SerializationException</ExceptionType>
<StackTrace> ... </StackTrace>
</InnerException>
</Error>
Salah satu solusinya adalah menggunakan DTO, yang saya jelaskan di bagian berikutnya. Atau, Anda dapat mengonfigurasi pemformat JSON dan XML untuk menangani siklus grafik. Untuk informasi selengkapnya, lihat Menangani Referensi Objek Melingkar.
Untuk tutorial ini, Anda tidak memerlukan Author.Book
properti navigasi, sehingga Anda dapat meninggalkannya.