Condividi tramite


Gestione delle relazioni tra entità

Scaricare il progetto completato

In questa sezione vengono descritti alcuni dettagli sul modo in cui EF carica le entità correlate e come gestire le proprietà di spostamento circolare nelle classi di modello. Questa sezione fornisce informazioni in background e non è necessaria per completare l'esercitazione. Se si preferisce, passare alla parte 5..

Caricamento ansioso rispetto al caricamento lazy

Quando si usa EF con un database relazionale, è importante comprendere in che modo EF carica i dati correlati.

È anche utile visualizzare le query SQL generate da EF. Per tracciare SQL, aggiungere la riga di codice seguente al BookServiceContext costruttore:

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

Se si invia una richiesta GET a /api/books, restituisce JSON come segue:

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

È possibile notare che la proprietà Author è null, anche se il libro contiene un AuthorId valido. Questo perché EF non carica le entità Author correlate. Il log di traccia della query SQL conferma quanto segue:

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]

L'istruzione SELECT prende dalla tabella Books e non fa riferimento alla tabella Author.

Per riferimento, ecco il metodo nella classe che restituisce l'elenco BooksController dei libri.

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

Vediamo come restituire l'autore come parte dei dati JSON. Esistono tre modi per caricare i dati correlati in Entity Framework: caricamento ansioso, caricamento lazy e caricamento esplicito. Ci sono compromessi con ogni tecnica, quindi è importante capire come funzionano.

Caricamento eager

Con il caricamento ansioso, EF carica le entità correlate come parte della query di database iniziale. Per eseguire il caricamento ansioso, usare il metodo di estensione System.Data.Entity.Include .

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

Indica a EF di includere i dati Di creazione nella query. Se si apporta questa modifica ed esegui l'app, i dati JSON sono simili al seguente:

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

Il log di traccia mostra che EF ha eseguito un join nelle tabelle Book and 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]

Caricamento lazy

Con il caricamento ritardato, EF carica automaticamente un'entità correlata quando la proprietà di spostamento per tale entità viene dereferenziata. Per abilitare il caricamento lazy, rendere virtuale la proprietà di spostamento. Ad esempio, nella classe Book:

public class Book
{
    // (Other properties)

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

Prendere ora in considerazione il codice seguente:

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

Quando il caricamento in ritardo è abilitato, l'accesso alla Author proprietà books[0] causa la query di EF sul database per l'autore.

Il caricamento in ritardo richiede più viaggi di database, perché EF invia una query ogni volta che recupera un'entità correlata. In genere, si vuole che il caricamento lazy sia disabilitato per gli oggetti serializzati. Il serializzatore deve leggere tutte le proprietà nel modello, che attiva il caricamento delle entità correlate. Ad esempio, ecco le query SQL quando EF serializza l'elenco di libri con caricamento ritardato abilitato. È possibile notare che EF effettua tre query separate per i tre autori.

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

Ci sono ancora momenti in cui si potrebbe voler usare il caricamento lazy. Il caricamento ansioso può causare la generazione di un join molto complesso. Oppure potrebbero essere necessarie entità correlate per un piccolo subset di dati e il caricamento in ritardo sarebbe più efficiente.

Un modo per evitare problemi di serializzazione consiste nel serializzare oggetti di trasferimento dati (DTOs) anziché oggetti di entità. Questo approccio verrà illustrato più avanti nell'articolo.

Caricamento esplicito

Il caricamento esplicito è simile al caricamento lazy, ad eccezione del fatto che si ottengono in modo esplicito i dati correlati nel codice; non avviene automaticamente quando si accede a una proprietà di spostamento. Il caricamento esplicito consente di controllare più quando caricare i dati correlati, ma richiede codice aggiuntivo. Per altre informazioni sul caricamento esplicito, vedere Caricamento di entità correlate.

Quando ho definito i modelli Book and Author, ho definito una proprietà di spostamento nella classe per la Book relazione di Book-Author, ma non ho definito una proprietà di navigazione nell'altra direzione.

Cosa accade se si aggiunge la proprietà di spostamento corrispondente alla Author classe?

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

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

Sfortunatamente, questo crea un problema quando si serializza i modelli. Se si caricano i dati correlati, crea un grafico a oggetti circolari.

Diagramma che mostra la classe Book che carica la classe Author e viceversa, creando un grafico a oggetti circolari.

Quando il formattatore JSON o XML tenta di serializzare il grafico, genererà un'eccezione. I due formattatori generano messaggi di eccezione diversi. Ecco un esempio per il formattatore 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": "..."
     }
}

Ecco il formattatore 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>

Una soluzione consiste nell'usare DTOs, descritta nella sezione successiva. In alternativa, è possibile configurare i formattatori JSON e XML per gestire i cicli dei grafici. Per altre informazioni, vedere Gestione dei riferimenti agli oggetti circolari.

Per questa esercitazione non è necessaria la Author.Book proprietà di spostamento, quindi è possibile lasciarla fuori.