Entitáskapcsolatok kezelése

Befejezett projekt letöltése

Ez a szakasz ismerteti a kapcsolódó entitások EF-betöltésének néhány részletét, valamint a körkörös navigációs tulajdonságok kezelését a modellosztályokban. (Ez a szakasz háttérismereteket biztosít, és nem szükséges az oktatóanyag elvégzéséhez. Ha szeretné, ugorjon az 5. részre.)

Eager Loading és Lazy Loading

Ha relációs adatbázissal használja az EF-t, fontos tisztában lenni azzal, hogy az EF hogyan tölti be a kapcsolódó adatokat.

Hasznos az EF által generált SQL-lekérdezések megtekintése is. Az SQL nyomon követéséhez adja hozzá a következő kódsort a BookServiceContext konstruktorhoz:

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

Ha GET kérést küld az /api/books szolgáltatásnak, az a JSON-t az alábbihoz hasonlóan adja vissza:

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

Láthatja, hogy a Szerző tulajdonság null értékű, annak ellenére, hogy a könyv érvényes AuthorId azonosítót tartalmaz. Ennek az az oka, hogy az EF nem tölti be a kapcsolódó Author entitásokat. Az SQL-lekérdezés nyomkövetési naplója a következőt erősíti meg:

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]

A SELECT utasítás a Könyvek táblából származik, és nem hivatkozik a Szerző táblára.

Referenciaként itt található az osztály azon BooksController metódusa, amely a könyvek listáját adja vissza.

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

Lássuk, hogyan tudjuk visszaadni a Szerzőt a JSON-adatok részeként. Az Entity Frameworkben háromféleképpen tölthet be kapcsolódó adatokat: lelkes betöltést, lusta betöltést és explicit betöltést. Minden technikával vannak kompromisszumok, ezért fontos megérteni, hogyan működnek.

Lelkes betöltés

A lelkes betöltéssel az EF betölti a kapcsolódó entitásokat a kezdeti adatbázis-lekérdezés részeként. A gyors betöltéshez használja a System.Data.Entity.Include bővítménymetódust.

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

Ez azt jelzi, hogy az EF belefoglalja a Szerző adatokat a lekérdezésbe. Ha ezt a módosítást elvégezve futtatja az alkalmazást, a JSON-adatok a következőképpen néznek ki:

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

A nyomkövetési napló azt mutatja, hogy az EF végrehajtott egy összekapcsolást a Könyv és a Szerző táblákon.

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]

lusta betöltés

Lusta betöltés esetén az EF automatikusan betölt egy kapcsolódó entitást, amikor az entitás navigációs tulajdonsága elérhetővé válik. A lusta betöltés engedélyezéséhez tegye virtuálissá a navigációs tulajdonságot. Például a Könyv osztályban:

public class Book
{
    // (Other properties)

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

Most vegye figyelembe a következő kódot:

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

Amikor a lusta betöltés engedélyezve van, a Author tulajdonság elérésekor az books[0] EF lekérdezi az adatbázisból a szerző adatait.

A lusta betöltéshez több adatbázis-utazásra van szükség, mivel az EF minden alkalommal küld lekérdezést, amikor egy kapcsolódó entitást kér le. Általában le szeretné tiltani a lusta betöltést a szerializált objektumok esetében. A szerializálónak be kell olvasnia a modell összes tulajdonságát, ami elindítja a kapcsolódó entitások betöltését. Ilyenek például az SQL-lekérdezések, amikor az EF szerializálja a lusta betöltést engedélyező könyvek listáját. Láthatja, hogy az EF három külön lekérdezést készít a három szerző számára.

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

Még mindig vannak olyan esetek, amikor halasztott betöltést szeretne használni. A türelmetlen betöltéssel az EF nagyon összetett illesztést hozhat létre. Vagy előfordulhat, hogy az adatok egy kis részhalmazához kapcsolódó entitásokra van szüksége, és a lusta betöltés hatékonyabb lenne.

A szerializálási problémák elkerülésének egyik módja az adatátviteli objektumok (DTO-k) szerializálása entitásobjektumok helyett. Ezt a megközelítést a cikk későbbi részében mutatom be.

Explicit betöltés

Az explicit betöltés hasonló a lusta betöltéshez, azzal a kivételnel, hogy a kapcsolódó adatokat kifejezetten kódban kapja meg; ez nem történik meg automatikusan egy navigációs tulajdonság elérésekor. Az explicit betöltéssel jobban szabályozhatja, hogy mikor kell betölteni a kapcsolódó adatokat, de további kódot igényel. A explicit betöltéssel kapcsolatos további információkért lásd a Kapcsolódó entitások betöltése című témakört.

Amikor meghatároztam a Könyv és szerző modelleket, meghatároztam egy navigációs tulajdonságot az osztályban a Book Book-Author kapcsolathoz, de nem definiáltam navigációs tulajdonságot a másik irányba.

Mi történik, ha hozzáadja a megfelelő navigációs tulajdonságot az Author osztályhoz?

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

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

Ez sajnos problémát okoz a modellek szerializálása során. Ha betölti a kapcsolódó adatokat, az egy körkörös objektumdiagramot hoz létre.

Diagram, amely azt mutatja, hogy a Könyv osztály betölti a Szerző osztályt, és fordítva, körkörös objektumdiagramot hoz létre.

Amikor a JSON- vagy XML-formázó megpróbálja szerializálni a gráfot, kivételt fog eredményezni. A két formázó különböző kivételüzeneteket küld. Íme egy példa a JSON-formátumolóra:

{
  "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": "..."
     }
}

Itt található az XML-formázó:

<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>

Az egyik megoldás a DTO-k használata, amelyet a következő szakaszban ismertetek. Másik lehetőségként konfigurálhatja a JSON- és XML-formázókat a gráfciklusok kezeléséhez. További információ: Körkörös objektumhivatkozások kezelése.

Ebben az oktatóanyagban nincs szüksége a Author.Book navigációs tulajdonságra, így kihagyhatja.