zoukankan      html  css  js  c++  java
  • 【Web API系列教程】3.4 — 实战:处理数据(处理实体关系)

    前言

    本部分描写叙述了EF怎样载入相关实体的细节,而且怎样在你的模型类中处理环形导航属性。(本部分预备了背景知识,而这不是完毕这个教程所必须的。你也能够跳到第五节)

    预载入和延迟载入

    预载入和延迟载入的英文名称各自是Eager Loading和Lazy Loading。

    当EF与关系数据库一同使用时。了解EF是怎样载入相关数据是很重要的。

    去查看EF生成的SQL查询也是很有帮助的。

    为了追踪SQL,加入下列代码到BookServiceContext构造器中:

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

    假设发送一个GET请求到/api/books,它返回像以下这种JSON:

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

    你能看到Author属性是空的,即便book包括有效的AuthorId。

    那是由于EF没有在载入相关的Author实体。关于SQL查询的跟踪日志例如以下:

    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]
    

    该SQL跟踪在Visual Studio的Output窗体中显示。——译者注

    SELECT语句从Books表中获取数据,但并没有引用Author表。
    作为參考,这里是在BooksController类中的方法。它返回books的列表。

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

    来看看我们怎样才干让Author作为返回的JSON数据的一部分。在Entity Framework中有三种方式载入相关数据:预载入(eager loading)、延迟载入(lazy loading)和显式载入(explicit loading)。我们应该在这三种技术中有所取舍,所以了解它们是怎样工作的就很重要了。

    Eager Loading(预载入)

    在预载入中,EF载入相关数据作为初始化数据库查询的一部分。

    为了运行预载入,使用System.Data.Entity.Include扩展方法。

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

    这会告诉EF将Author数据包括在查询中。假设你做了这个改变并运行了app。如今JSON数据会是例如以下所看到的:

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

    其跟踪日志显示EF在Book和Author表中运行了一个join操作。

    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]
    

    Lazy Loading(延迟载入)

    在延迟载入中,当实体的导航属性是非关联时,EF会自己主动载入一个相关的实体。为了使用延迟载入。使导航属性变成虚拟的。

    比如。在Book类中:

    public class Book
    {
        // (Other properties)
    
        // Virtual navigation property
        public virtual Author Author { get; set; }
    }
    

    如今考虑例如以下代码:

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

    当延迟载入开启时。在books[0]上訪问Author属性会使EF为author查询数据库。

    延迟载入须要多段数据库操作过程。由于每次EF发送一个查询它都会取出一次相关实体。

    通常。你希望为序列化的对象禁用延迟载入。序列化已经在模型上读取了全部可能触发载入相关实体的属性。比如,以下是当延迟载入开启后EF序列化books列表时的SQL查询。

    你能够看到EF对于三个作者做了三次不同的查询。

    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
    

    但还有很多时候你可能想要使用延迟载入。

    预载入会造成EF生成很复杂的联接。或者你可能须要对于小的数据集合的相关实体。延迟载入会更加有效。

    避免序列化问题的一种方式是序列化传输数据对象(DTOs)而不是实体对象。我将会在后面的文章中展示这种实现。

    显式载入(Explicit Loading)

    显式载入和延迟载入很相似,除了你在代码中显式地获取相关数据;当你訪问导航属性时它不会自己主动发生。

    显示载入会在载入相关数据时给你很多其它的控制权,但也须要额外的代码。关于显示载入的很多其它信息,请查看Loading Related Entities。

    http://msdn.microsoft.com/en-us/data/jj574232#explicit

    导航属性和环形引用(Navigation Properties and Circular References)

    当我定义Book和Author模型时。我在Book类中为Book-Author关系定义了导航属性。但我没有在其它方向定义导航属性。

    假设你在Author类中也定义对应的导航属性会怎样呢?

    public class Author
    {
        public int AuthorId { get; set; }
        [Required]
        public string Name { get; set; }
    
        public ICollection<Book> Books { get; set; }
    }
    

    不幸的是。当你在序列化模型时这会产生一个问题。

    假设你载入相关数据,它会产生环形对象图。

    这里写图片描写叙述

    当JSON或XML格式试图序列化图时。它将会抛出一个异常。

    这两个格式抛出不同异常信息。这里是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": "...”
         }
    }
    

    这里是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>
    

    一个解决方式是使用DTO,我将会在下一节中描写叙述它。

    你能够配置JSON或XML格式化程序来处理图循环。关于很多其它信息,请查看Handling Circular Object References. (http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization#handling_circular_object_references)

    对于本教程。你不须要Author.Book导航熟悉,所以你能够去掉它。

  • 相关阅读:
    CF896C Willem, Chtholly and Seniorious 珂朵莉树
    LG2495 「SDOI2011」消耗战 虚树
    20191102 「HZOJ NOIP2019 Round #12」20191102模拟
    LG1345 「USACO5.4」Telecowmunication 最小割
    LG1344 「USACO4.4」Pollutant Control 最小割
    POJ1741 Tree 点分治
    [BZOJ2143]飞飞侠 并查集优化最短路
    [NOI.AC#41]最短路 线性基
    [NOI.AC#40]Erlang
    [BZOJ2238]Mst 最小生成树+树链剖分/并查集
  • 原文地址:https://www.cnblogs.com/cynchanpin/p/7101320.html
Copyright © 2011-2022 走看看