Ošetření relací prvků

Stažení dokončeného projektu

Tato část popisuje některé podrobnosti o tom, jak EF načítá související entity a jak zpracovávat vlastnosti cyklické navigace ve třídách modelu. (Tato část poskytuje základní znalosti a k dokončení kurzu se nevyžaduje. Pokud chcete, přeskočte na část 5..

Dychtivé načítání versus opoždě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é zobrazit 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 na adresu /api/books, vrátí kód JSON podobný následujícímu:

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

Můžete vidět, že Author vlastnost má hodnotu null, i když kniha obsahuje platné AuthorId. Ef totiž nenačítá související entity Author. Protokol trasování dotazu SQL to potvrzuje:

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 pochází z tabulky Books a neodkazuje na tabulku Author.

Pro referenci je zde metoda ve BooksController třídě , která vrací seznam knih.

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

Podívejme se, jak můžeme v rámci dat JSON vrátit autora. Existují tři způsoby, jak v Entity Framework načíst související data: nedočkavé načítání, opožděné načítání a explicitní načítání. S každou technikou existují kompromisy, proto je důležité pochopit, jak fungují.

Dychtivé načítání

Ef při nedočkavém načítání načítá související entity jako součást počátečního databázového dotazu. Pokud chcete provést dychtivé načítání, použijte rozšiřující metodu System.Data.Entity.Include .

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

Ef tím sděluje, že má do dotazu zahrnout data Autora. Pokud provedete tuto změnu a spustíte aplikaci, budou teď data JSON vypadat 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 Book a 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]

Opožděné načítání

Při opožděné načítání EF automaticky načte související entitu, když se vlastnost navigace pro tuto entitu přesferencuje. Pokud chcete povolit opožděné načítání, nastavte vlastnost navigace jako virtuální. Například ve třídě Book:

public class Book
{
    // (Other properties)

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

Teď se podívejte na 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 na books[0] způsobí, že EF se dotazuje databáze na autora.

Opožděné načítání vyžaduje několik databázových cest, protože EF odešle dotaz pokaždé, když načte související entitu. Obecně platí, že chcete opožděné načítání zakázáno pro objekty, které serializujete. Serializátor musí číst všechny vlastnosti modelu, což aktivuje 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 pro tři autory vytvoří tři samostatné dotazy.

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í časy, kdy můžete chtít použít opožděné načítání. Náročné načítání 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ů entit. Tento přístup si ukážu později v článku.

Explicitní načítání

Explicitní načítání je podobné opožděné načítání, s tím rozdílem, že explicitně získáte související data v kódu; nedojde k tomu automaticky při přístupu k vlastnosti navigace. Explicitní načítání poskytuje větší kontrolu nad tím, kdy načíst související data, ale vyžaduje další kód. Další informace o explicitním načítání najdete v tématu Načítání souvisejících entit.

Při definování modelů Book a Author jsem definoval vlastnost navigace ve Book třídě pro vztah Book-Author, ale nedefinoval jsem vlastnost navigace 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; }
}

Bohužel to vytváří problém při serializaci modelů. Pokud načtete související data, vytvoří se graf kruhového objektu.

Diagram znázorňující třídu Book, která načítá třídu Author a naopak, vytváří kruhový graf objektů

Když se formátovací modul JSON nebo XML pokusí o serializaci grafu, vyvolá výjimku. Tyto 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í kód 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 pro zpracování cyklů grafů. Další informace najdete v tématu Zpracování cyklických odkazů na objekty.

Pro účely tohoto kurzu nepotřebujete Author.Book vlastnost navigace, takže ji můžete vynechat.