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.
Navigationseigenschaften und Zirkelverweise
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.
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.