Obsługa relacji jednostek

Pobieranie ukończonego projektu

W tej sekcji opisano niektóre szczegóły dotyczące ładowania powiązanych jednostek przez program EF oraz sposobu obsługi właściwości nawigacji cyklicznej w klasach modelu. (Ta sekcja zawiera wiedzę w tle i nie jest wymagana do ukończenia samouczka. Jeśli wolisz, przejdź do części 5.).

Ładowanie chętne a ładowanie z opóźnieniem

W przypadku korzystania z platformy EF z relacyjnej bazy danych ważne jest, aby zrozumieć, jak ef ładuje powiązane dane.

Przydatne jest również sprawdzenie zapytań SQL generowanych przez program EF. Aby śledzić kod SQL, dodaj następujący wiersz kodu do konstruktora BookServiceContext :

public BookServiceContext() : base("name=BookServiceContext")
{
    // New code:
    this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
}

Jeśli wysyłasz żądanie GET do /api/books, zwraca on kod JSON podobny do następującego:

[
  {
    "BookId": 1,
    "Title": "Pride and Prejudice",
    "Year": 1813,
    "Price": 9.99,
    "Genre": "Comedy of manners",
    "AuthorId": 1,
    "Author": null
  },
  ...

Widać, że właściwość Author ma wartość null, mimo że książka zawiera prawidłowy identyfikator AuthorId. Dzieje się tak, ponieważ program EF nie ładuje powiązanych jednostek Author. Dziennik śledzenia zapytania SQL potwierdza to:

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]

Instrukcja SELECT pobiera z tabeli Books i nie odwołuje się do tabeli Author.

Aby uzyskać odwołanie, oto metoda w BooksController klasie, która zwraca listę książek.

public IQueryable<Book> GetBooks()
{
    return db.Books;
}

Zobaczmy, jak możemy zwrócić autora jako część danych JSON. Istnieją trzy sposoby ładowania powiązanych danych w programie Entity Framework: chętne ładowanie, ładowanie leniwe i jawne ładowanie. Istnieją kompromisy z każdą techniką, więc ważne jest, aby zrozumieć, jak działają.

Chętny do ładowania

W przypadku chętnego ładowania program EF ładuje powiązane jednostki w ramach początkowego zapytania bazy danych. Aby przeprowadzić ładowanie chętne, użyj metody rozszerzenia System.Data.Entity.Include .

public IQueryable<Book> GetBooks()
{
    return db.Books
        // new code:
        .Include(b => b.Author);
}

Dzięki temu program EF będzie uwzględniał dane autora w zapytaniu. Jeśli wprowadzisz tę zmianę i uruchomisz aplikację, teraz dane JSON będą wyglądać następująco:

[
  {
    "BookId": 1,
    "Title": "Pride and Prejudice",
    "Year": 1813,
    "Price": 9.99,
    "Genre": "Comedy of manners",
    "AuthorId": 1,
    "Author": {
      "AuthorId": 1,
      "Name": "Jane Austen"
    }
  },
  ...

Dziennik śledzenia pokazuje, że program EF wykonał sprzężenia w tabelach Book and Author.

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]

Ładowanie z opóźnieniem

W przypadku ładowania z opóźnieniem program EF automatycznie ładuje powiązaną jednostkę, gdy właściwość nawigacji dla tej jednostki jest wyłuszczana. Aby włączyć ładowanie leniwe, ustaw właściwość nawigacji jako wirtualną. Na przykład w klasie Book:

public class Book
{
    // (Other properties)

    // Virtual navigation property
    public virtual Author Author { get; set; }
}

Teraz rozważ następujący kod:

var books = db.Books.ToList();  // Does not load authors
var author = books[0].Author;   // Loads the author for books[0]

Po włączeniu Author ładowania z opóźnieniem uzyskiwanie dostępu do właściwości powoduje books[0] , że program EF wysyła zapytanie do bazy danych dla autora.

Ładowanie z opóźnieniem wymaga wielu podróży bazy danych, ponieważ program EF wysyła zapytanie za każdym razem, gdy pobiera powiązaną jednostkę. Ogólnie rzecz biorąc, chcesz wyłączyć ładowanie z opóźnieniem dla obiektów, które serializujesz. Serializator musi odczytać wszystkie właściwości modelu, co wyzwala ładowanie powiązanych jednostek. Oto na przykład zapytania SQL, gdy EF serializuje listę książek z włączonym ładowaniem leniwym. Widać, że ef tworzy trzy oddzielne zapytania dla trzech autorów.

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

Nadal są czasy, kiedy warto użyć leniwego ładowania. Chętne ładowanie może spowodować wygenerowanie bardzo złożonego sprzężenia ef. Możesz też potrzebować powiązanych jednostek dla małego podzestawu danych, a ładowanie leniwe byłoby bardziej wydajne.

Jednym ze sposobów uniknięcia problemów z serializacji jest serializowanie obiektów transferu danych (DTOs) zamiast obiektów jednostek. Pokażę to podejście w dalszej części artykułu.

Jawne ładowanie

Jawne ładowanie jest podobne do ładowania leniwego, z wyjątkiem tego, że jawnie pobierasz powiązane dane w kodzie; nie występuje automatycznie po uzyskiwaniu dostępu do właściwości nawigacji. Jawne ładowanie zapewnia większą kontrolę nad tym, kiedy ładować powiązane dane, ale wymaga dodatkowego kodu. Aby uzyskać więcej informacji na temat jawnego ładowania, zobacz Ładowanie powiązanych jednostek.

Po zdefiniowaniu modeli Książki i Autora zdefiniowałem właściwość nawigacji w Book klasie dla relacji Book-Author, ale nie zdefiniowałem właściwości nawigacji w innym kierunku.

Co się stanie w przypadku dodania odpowiedniej właściwości nawigacji do Author klasy?

public class Author
{
    public int AuthorId { get; set; }
    [Required]
    public string Name { get; set; }

    public ICollection<Book> Books { get; set; }
}

Niestety, powoduje to problem podczas serializacji modeli. W przypadku załadowania powiązanych danych zostanie utworzony wykres obiektu cyklicznego.

Diagram przedstawiający klasę Book ładującej klasę Author i odwrotnie tworząc wykres obiektu cyklicznego.

Gdy formater JSON lub XML próbuje serializować wykres, zgłosi wyjątek. Oba formatery zgłaszają różne komunikaty o wyjątkach. Oto przykład dla formatu 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": "..."
     }
}

Oto formater 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>

Jednym z rozwiązań jest użycie obiektów DTO, które opisano w następnej sekcji. Alternatywnie można skonfigurować formatery JSON i XML do obsługi cykli grafu. Aby uzyskać więcej informacji, zobacz Obsługa odwołań do obiektów cyklicznych.

Na potrzeby tego samouczka Author.Book nie potrzebujesz właściwości nawigacji, więc możesz ją pominąć.