Freigeben über


Verarbeiten von Entitätsbeziehungen

Abgeschlossenes Projekt herunterladen

In diesem Abschnitt werden einige Details dazu beschrieben, wie EF verwandte Entitäten lädt und wie Sie Die Zirkelnavigationseigenschaften in Ihren Modellklassen behandeln. (Dieser Abschnitt enthält Hintergrundwissen und ist nicht erforderlich, um das Tutorial abzuschließen. Wenn Sie möchten, fahren Sie mit Teil 5 fort..)

Eager Loading im Vergleich zu verzögerten Ladevorgängen

Bei der Verwendung von EF mit einer relationalen Datenbank ist es wichtig zu verstehen, wie EF verwandte Daten lädt.

Es ist auch nützlich, die SQL-Abfragen anzuzeigen, die EF generiert. Fügen Sie zum Nachverfolgen von SQL dem Konstruktor die BookServiceContext folgende Codezeile hinzu:

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

Wenn Sie eine GET-Anforderung an /api/books senden, wird JSON wie folgt zurückgegeben:

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

Sie können sehen, dass die Author-Eigenschaft NULL ist, obwohl das Buch eine gültige AuthorId enthält. Dies liegt daran, dass EF die zugehörigen Author-Entitäten nicht lädt. Das Ablaufverfolgungsprotokoll der SQL-Abfrage bestätigt Folgendes:

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]

Die SELECT-Anweisung stammt aus der Tabelle Books und verweist nicht auf die Tabelle Author.

Hier ist die -Methode in der -Klasse, die BooksController die Liste der Bücher zurückgibt.

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

Sehen wir uns an, wie wir den Autor als Teil der JSON-Daten zurückgeben können. Es gibt drei Möglichkeiten zum Laden verwandter Daten in Entity Framework: Eager Loading, Verzögertes Laden und explizites Laden. Es gibt Kompromisse mit jeder Technik, daher ist es wichtig, zu verstehen, wie sie funktionieren.

Eager Loading

Beim eifrigen Laden lädt EF verwandte Entitäten als Teil der anfänglichen Datenbankabfrage. Verwenden Sie die System.Data.Entity.Include-Erweiterungsmethode , um eager loading auszuführen.

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

Dadurch wird EF aufgefordert, die Author-Daten in die Abfrage einzuschließen. Wenn Sie diese Änderung vornehmen und die App ausführen, sehen die JSON-Daten jetzt wie folgt aus:

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

Das Ablaufverfolgungsprotokoll zeigt, dass EF einen Join für die Tabellen Book und Author ausgeführt hat.

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

Bei verzögertem Laden lädt EF automatisch eine zugehörige Entität, wenn die Navigationseigenschaft für diese Entität dereferenziert wird. Um verzögertes Laden zu aktivieren, machen Sie die Navigationseigenschaft virtuell. Beispiel: In der Book-Klasse:

public class Book
{
    // (Other properties)

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

Betrachten Sie nun den folgenden Code:

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

Wenn verzögertes Laden aktiviert ist, führt der Zugriff auf die Author Eigenschaft für books[0] dazu, dass EF die Datenbank für den Autor abfragt.

Verzögertes Laden erfordert mehrere Datenbanktrips, da EF bei jedem Abrufen einer verknüpften Entität eine Abfrage sendet. Im Allgemeinen möchten Sie verzögertes Laden für objekte deaktivieren, die Sie serialisieren. Das Serialisierungsprogramm muss alle Eigenschaften des Modells lesen, wodurch das Laden der zugehörigen Entitäten ausgelöst wird. Hier sind beispielsweise die SQL-Abfragen aufgeführt, wenn EF die Liste der Bücher mit aktiviertem verzögertem Laden serialisiert. Sie können sehen, dass EF drei separate Abfragen für die drei Autoren erstellt.

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

Es gibt immer noch Situationen, in denen Sie verzögertes Laden verwenden möchten. Eifriges Laden kann dazu führen, dass EF eine sehr komplexe Verknüpfung generiert. Oder Sie benötigen verwandte Entitäten für eine kleine Teilmenge der Daten, und verzögertes Laden wäre effizienter.

Eine Möglichkeit, Serialisierungsprobleme zu vermeiden, besteht darin, Datenübertragungsobjekte (Data Transfer Objects, DTOs) anstelle von Entitätsobjekten zu serialisieren. Ich werde diesen Ansatz später im Artikel zeigen.

Explizites Laden

Das explizite Laden ähnelt dem verzögerten Laden, mit der Ausnahme, dass Sie die zugehörigen Daten explizit im Code abrufen. Dies geschieht nicht automatisch, wenn Sie auf eine Navigationseigenschaft zugreifen. Das explizite Laden ermöglicht Ihnen mehr Kontrolle darüber, wann verwandte Daten geladen werden sollen, erfordert jedoch zusätzlichen Code. Weitere Informationen zum expliziten Laden finden Sie unter Laden verwandter Entitäten.

Als ich die Modelle Book und Author definiert habe, habe ich eine Navigationseigenschaft für die Book -Klasse für die Book-Author Beziehung definiert, aber ich habe keine Navigationseigenschaft in die andere Richtung definiert.

Was geschieht, wenn Sie der Klasse die entsprechende Navigationseigenschaft Author hinzufügen?

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

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

Dies führt leider zu einem Problem, wenn Sie die Modelle serialisieren. Wenn Sie die verknüpften Daten laden, wird ein kreisförmiges Objektdiagramm erstellt.

Diagramm, das die Book-Klasse zeigt, die die Author-Klasse lädt und umgekehrt, wodurch ein kreisförmiger Objektgraph erstellt wird.

Wenn der JSON- oder XML-Formatierer versucht, das Diagramm zu serialisieren, wird eine Ausnahme ausgelöst. Die beiden Formatierer lösen unterschiedliche Ausnahmemeldungen aus. Hier ist ein Beispiel für den JSON-Formatierer:

{
  "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": "..."
     }
}

Hier ist der XML-Formatierer:

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

Eine Lösung besteht darin, DTOs zu verwenden, die ich im nächsten Abschnitt beschreibe. Alternativ können Sie die JSON- und XML-Formatierer konfigurieren, um Graphzyklen zu verarbeiten. Weitere Informationen finden Sie unter Behandeln von Zirkelobjektverweise.

Für dieses Tutorial benötigen Sie die Author.Book Navigationseigenschaft nicht, sodass Sie sie weglassen können.