当前位置: 移动技术网 > IT编程>开发语言>.net > 【Web API系列教程】3.4 — 实战:处理数据(处理实体关系)

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

2018年10月27日  | 移动技术网IT编程  | 我要评论

乐燕照片,卢钟鹤,煤矿工人工作总结

前言

本部分描述了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; }
}

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

这里写图片描述

当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导航熟悉,所以你可以去掉它。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网