Menangani Hubungan Entitas

Unduh Proyek Yang Selesai

Bagian ini menjelaskan beberapa detail tentang bagaimana Entity Framework (EF) memuat entitas terkait, dan bagaimana menangani properti navigasi melingkar dalam kelas model Anda. (Bagian ini memberikan pengetahuan latar belakang, dan tidak diperlukan untuk menyelesaikan tutorial. Jika mau, lewati ke Bagian 5..)

Eager Loading versus Lazy Loading

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 Penulis adalah null, meskipun buku tersebut 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 Penulis sebagai bagian dari data JSON. Ada tiga cara untuk memuat data terkait dalam Entity Framework: eager loading, lazy loading, dan pemuatan eksplisit. Ada kompromi dalam setiap teknik, jadi penting untuk memahami bagaimana mereka bekerja.

Pemuatan Eager

Dengan eager loading, EF memuat entitas terkait sebagai bagian dari kueri database awal. Untuk melakukan pemuatan awal, 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]

Lazy Loading (Pemuatan Malas)

Dengan lazy loading, EF secara otomatis memuat entitas terkait saat properti navigasinya diakses. 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 lazy loading diaktifkan, mengakses properti Author pada books[0] menyebabkan EF melakukan kueri ke database untuk mendapatkan data penulis.

Lazy loading (pemuatan malas) memerlukan beberapa permintaan ke database, karena EF mengirim kueri setiap kali mengakses entitas terkait. Umumnya, Anda ingin lazy loading dinonaktifkan untuk objek yang Anda serialkan. Serializer harus membaca semua properti pada model, yang memicu pemuatan entitas terkait. Misalnya, berikut adalah kueri SQL ketika EF menyerialisasikan daftar buku dengan lazy loading 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 lazy loading. Pemuatan cepat dapat menyebabkan EF menghasilkan gabungan yang sangat kompleks. Atau Anda mungkin memerlukan entitas terkait untuk subset kecil data, dan lazy loading 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 lambat, kecuali bahwa Anda harus mendapatkan data terkait secara eksplisit dalam kode. Hal ini tidak terjadi secara otomatis ketika 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.

Ketika saya menentukan 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 saat Anda membuat serialisasi model. Jika Anda memuat data terkait, data tersebut akan membuat grafik objek melingkar.

Diagram yang menunjukkan kelas Buku yang memuat kelas Penulis dan begitu juga sebaliknya, membuat grafik objek sirkular.

Ketika formatter JSON atau XML mencoba menserialisasikan grafik, itu akan melemparkan pengecualian. Kedua formatter melemparkan pesan pengecualian yang berbeda. Berikut adalah contoh untuk formatter 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 formatter 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 formatter 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.