欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【Web API系列教程】3.4 — 实战:处理数据(处理实体关系)

程序员文章站 2023-08-25 23:48:45
前言 本部分描述了ef如何加载相关实体的细节,并且如何在你的模型类中处理环形导航属性。(本部分预备了背景知识,而这不是完成这个教程所必须的。你也可以跳到第五节) 预加载和延迟加载 预加载和延迟加载的...

前言

本部分描述了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 getbooks()
{
    return db.books;
}

来看看我们如何才能让author作为返回的json数据的一部分。在entity framework中有三种方式加载相关数据:预加载(eager loading)、延迟加载(lazy loading)和显式加载(explicit loading)。我们应该在这三种技术中有所取舍,所以了解它们是如何工作的就非常重要了。

eager loading(预加载)

在预加载中,ef加载相关数据作为初始化数据库查询的一部分。为了执行预加载,使用system.data.entity.include扩展方法。

public iqueryable 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。 

导航属性和环形引用(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 books { get; set; }
}

不幸的是,当你在序列化模型时这会产生一个问题。如果你加载相关数据,它会产生环形对象图。

【Web API系列教程】3.4 — 实战:处理数据(处理实体关系)

当json或xml格式试图序列化图时,它将会抛出一个异常。这两个格式抛出不同异常信息。这里是json格式的示例:喎? f/ware/vc/"="" target="_blank" class="keylink">vcd4ncjxwcmugy2xhc3m9"brush:java;"> { "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格式的示例:

<code class=" hljs 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>
</stacktrace></error>
</code>

一个解决方案是使用dto,我将会在下一节中描述它。你可以配置json或xml格式化程序来处理图循环。关于更多信息,请查看handling circular object references. (https://www..net/web-api/overview/formats-and-model-binding/json-and-xml-serialization)

对于本教程,你不需要author.book导航熟悉,所以你可以去掉它。

喎?>