Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
I det här avsnittet beskrivs lite information om hur EF läser in relaterade entiteter och hur du hanterar cirkulära navigeringsegenskaper i dina modellklasser. (Det här avsnittet innehåller bakgrundskunskaper och krävs inte för att slutföra självstudien. Om du vill kan du gå vidare till del 5..)
Ivrig inläsning jämfört med lat inläsning
När du använder EF med en relationsdatabas är det viktigt att förstå hur EF läser in relaterade data.
Det är också användbart att se de SQL-frågor som EF genererar. Om du vill spåra SQL lägger du till följande kodrad i BookServiceContext konstruktorn:
public BookServiceContext() : base("name=BookServiceContext")
{
// New code:
this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
}
Om du skickar en GET-begäran till /api/books returneras JSON som följande:
[
{
"BookId": 1,
"Title": "Pride and Prejudice",
"Year": 1813,
"Price": 9.99,
"Genre": "Comedy of manners",
"AuthorId": 1,
"Author": null
},
...
Du kan se att egenskapen Author är null, även om boken innehåller ett giltigt AuthorId. Det beror på att EF inte läser in relaterade författaretiteter. Spårningsloggen för SQL-frågan bekräftar detta:
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-instruktionen hämtas från tabellen Böcker och refererar inte till tabellen Författare.
Som referens är här metoden i BooksController klassen som returnerar listan över böcker.
public IQueryable<Book> GetBooks()
{
return db.Books;
}
Nu ska vi se hur vi kan returnera författaren som en del av JSON-data. Det finns tre sätt att läsa in relaterade data i Entity Framework: ivrig inläsning, lat inläsning och explicit inläsning. Det finns kompromisser med varje teknik, så det är viktigt att förstå hur de fungerar.
Ivrig inläsning
Med ivrig inläsning läser EF in relaterade entiteter som en del av den första databasfrågan. Om du vill utföra ivrig inläsning använder du metoden System.Data.Entity.Include extension.
public IQueryable<Book> GetBooks()
{
return db.Books
// new code:
.Include(b => b.Author);
}
Detta instruerar EF att inkludera författardata i frågan. Om du gör den här ändringen och kör appen ser JSON-data ut så här:
[
{
"BookId": 1,
"Title": "Pride and Prejudice",
"Year": 1813,
"Price": 9.99,
"Genre": "Comedy of manners",
"AuthorId": 1,
"Author": {
"AuthorId": 1,
"Name": "Jane Austen"
}
},
...
Spårningsloggen visar att EF utförde en koppling i tabellerna Bok och Författare.
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]
Fördröjd laddning
Med lat inläsning läser EF automatiskt in en relaterad entitet när navigeringsegenskapen för den entiteten avrefereras. För att aktivera fördröjd inläsning gör du navigeringsegenskapen virtuell. Till exempel i klassen Bok:
public class Book
{
// (Other properties)
// Virtual navigation property
public virtual Author Author { get; set; }
}
Överväg nu följande kod:
var books = db.Books.ToList(); // Does not load authors
var author = books[0].Author; // Loads the author for books[0]
När lat inläsning är aktiverat orsakar åtkomst till Author-egenskapen på books[0] att EF gör en databasfråga efter författaren.
Lat inläsning kräver flera databasresor, eftersom EF skickar en fråga varje gång den hämtar en relaterad entitet. Generellt sett vill du att fördröjd inläsning ska inaktiveras för objekt som du serialiserar. Serialiseraren måste läsa alla egenskaper i modellen, vilket utlöser inläsning av relaterade entiteter. Här är till exempel SQL-frågorna när EF serialiserar listan över böcker med lat inläsning aktiverad. Du kan se att EF gör tre separata frågor för de tre författarna.
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
Det finns fortfarande tillfällen då du kanske vill använda lat inläsning. Ivrig inläsning kan få EF att generera en mycket komplex koppling. Eller så kan du behöva relaterade entiteter för en liten delmängd av data, och lazy loading skulle vara mer effektiv.
Ett sätt att undvika serialiseringsproblem är att serialisera dataöverföringsobjekt (DTU:er) i stället för entitetsobjekt. Jag ska visa den här metoden senare i artikeln.
Explicit inläsning
Explicit inläsning liknar lat inläsning, förutom att du uttryckligen hämtar relaterade data i kod. Det sker inte automatiskt när du kommer åt en navigeringsegenskap. Explicit inläsning ger dig mer kontroll över när du ska läsa in relaterade data, men kräver extra kod. Mer information om explicit inläsning finns i Läsa in relaterade entiteter.
Navigeringsegenskaper och cirkelreferenser
När jag definierade modellerna Bok och Författare definierade jag en navigeringsegenskap Book för klassen för Book-Author relation, men jag definierade inte en navigeringsegenskap i den andra riktningen.
Vad händer om du lägger till motsvarande navigeringsegenskap i Author klassen?
public class Author
{
public int AuthorId { get; set; }
[Required]
public string Name { get; set; }
public ICollection<Book> Books { get; set; }
}
Tyvärr skapar detta ett problem när du serialiserar modellerna. Om du läser in relaterade data skapas ett cirkulärt objektdiagram.
När JSON- eller XML-formaterare försöker serialisera grafen utlöser den ett undantag. De två formatrarna genererar olika undantagsmeddelanden. Här är ett exempel på JSON-formaterare:
{
"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": "..."
}
}
Här är XML-formaterare:
<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>
En lösning är att använda DTU:er, som jag beskriver i nästa avsnitt. Du kan också konfigurera JSON- och XML-formateringarna för att hantera grafcykler. Mer information finns i Hantera cirkelobjektreferenser.
I den här självstudien behöver du inte navigeringsegenskapen Author.Book , så du kan utelämna den.