Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tato část popisuje některé podrobnosti o tom, jak EF načítá související entity a jak zpracovávat vlastnosti kruhové navigace ve třídách modelu. (Tato část poskytuje základní znalosti a není nutná k dokončení kurzu. Pokud chcete, přejděte na část 5.)
Okamžité načítání versus Líné načítání
Při použití EF s relační databází je důležité pochopit, jak EF načítá související data.
Je také užitečné vidět dotazy SQL, které EF generuje. Pokud chcete trasovat SQL, přidejte do konstruktoru BookServiceContext následující řádek kódu:
public BookServiceContext() : base("name=BookServiceContext")
{
// New code:
this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
}
Pokud odešlete požadavek GET do /api/books, vrátí json takto:
[
{
"BookId": 1,
"Title": "Pride and Prejudice",
"Year": 1813,
"Price": 9.99,
"Genre": "Comedy of manners",
"AuthorId": 1,
"Author": null
},
...
Vidíte, že vlastnost Author má hodnotu null, i když kniha obsahuje platné Id autora. Důvodem je, že EF nenačítá entity související s Autorem. Protokol trasování dotazu SQL potvrzuje následující:
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]
Příkaz SELECT přebírá z tabulky Books a neodkazuje na tabulku Autor.
Pro referenci zde je metoda ve BooksController třídě, která vrací seznam knih.
public IQueryable<Book> GetBooks()
{
return db.Books;
}
Pojďme se podívat, jak můžeme autora vrátit jako součást dat JSON. Existují tři způsoby, jak načíst související data v Entity Frameworku: dychtivé načítání, opožděné načítání a explicitní načítání. S každou technikou existují kompromisy, takže je důležité pochopit, jak fungují.
Nedočkavé načítání
S dychtivým načítáním EF načte související entity jako součást počátečního databázového dotazu. K provádění dychtivého načítání použijte metodu rozšíření System.Data.Entity.Include .
public IQueryable<Book> GetBooks()
{
return db.Books
// new code:
.Include(b => b.Author);
}
Ef to říká, aby do dotazu zahrnula data autora. Pokud provedete tuto změnu a spustíte aplikaci, data JSON teď vypadají takto:
[
{
"BookId": 1,
"Title": "Pride and Prejudice",
"Year": 1813,
"Price": 9.99,
"Genre": "Comedy of manners",
"AuthorId": 1,
"Author": {
"AuthorId": 1,
"Name": "Jane Austen"
}
},
...
Protokol trasování ukazuje, že EF provedl spojení s tabulkami Knihy a Autor.
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]
Líné načítání
Při lazy loading EF automaticky načte související entitu, když je vlastnost navigace pro danou entitu dereferencována. Pokud chcete povolit opožděné načítání, nastavte navigační vlastnost jako virtuální. Například ve třídě Kniha:
public class Book
{
// (Other properties)
// Virtual navigation property
public virtual Author Author { get; set; }
}
Teď zvažte následující kód:
var books = db.Books.ToList(); // Does not load authors
var author = books[0].Author; // Loads the author for books[0]
Pokud je povolené opožděné načítání, přístup k Author vlastnosti books[0] způsobí, že EF dotazuje databázi pro autora.
Opožděné načítání vyžaduje více cest k databázi, protože EF odesílá dotaz pokaždé, když načte související entitu. Obecně platí, že pro objekty, které serializujete, chcete líné načítání zakázat. Serializátor musí číst všechny vlastnosti modelu, které aktivují načítání souvisejících entit. Tady jsou například dotazy SQL, když EF serializuje seznam knih s povoleným opožděným načítáním. Vidíte, že EF vytváří tři samostatné dotazy pro tři autory.
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
Stále existují chvíle, kdy můžete chtít používat opožděné načítání. Eager loading může způsobit, že EF vygeneruje velmi složité spojení. Nebo můžete potřebovat související entity pro malou podmnožinu dat a opožděné načítání by bylo efektivnější.
Jedním ze způsobů, jak se vyhnout problémům se serializací, je serializovat objekty přenosu dat (DTO) místo objektů entity. Tento přístup si ukážem později v článku.
Explicitní načítání
Explicitní načítání je podobné opožděnému načítání, avšak s tím rozdílem, že související data získáte kódem explicitně; k načítání nedochází automaticky, když přistupujete k navigační vlastnosti. Explicitní načítání vám dává větší kontrolu nad tím, kdy se mají načítat související data, ale vyžaduje další kód. Další informace o explicitním načítání naleznete v tématu Načítání souvisejících entit.
Navigační vlastnosti a cyklické odkazy
Když jsem definoval modely Knihy a Autor, definoval jsem navigační vlastnost třídy Book pro Book-Author vztah, ale nedefinoval jsem navigační vlastnost v opačném směru.
Co se stane, když do Author třídy přidáte odpovídající navigační vlastnost?
public class Author
{
public int AuthorId { get; set; }
[Required]
public string Name { get; set; }
public ICollection<Book> Books { get; set; }
}
Tento problém se bohužel vytvoří při serializaci modelů. Pokud načtete související data, vytvoří se kruhový graf objektu.
Když se formát JSON nebo XML pokusí serializovat graf, vyvolá výjimku. Dva formátovací moduly můžou vyvolat různé zprávy o výjimce. Tady je příklad formátovače 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": "..."
}
}
Tady je formátovací modul 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>
Jedním z řešení je použití DTO, které popíšem v další části. Alternativně můžete nakonfigurovat formátovací moduly JSON a XML tak, aby zpracovávaly cykly grafu. Další informace naleznete v tématu Zpracování cyklických odkazů na objekty.
Pro účely tohoto kurzu nepotřebujete Author.Book navigační vlastnost, takže ji můžete vynechat.