【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 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导航熟悉,所以你可以去掉它。
喎?>推荐阅读
-
【Web API系列教程】3.4 — 实战:处理数据(处理实体关系)
-
【Web API系列教程】3.8 — 实战:处理数据(显示条目细节)
-
【Web API系列教程】3.1 — 实战:处理数据(创建项目)
-
【Web API系列教程】3.3 — 实战:处理数据(建立数据库)
-
【Web API系列教程】3.10 — 实战:处理数据(发布App到Azure App Service)
-
【Web API系列教程】3.7 — 实战:处理数据(创建UI视图)
-
【Web API系列教程】3.5 — 实战:处理数据(创建数据传输对象)
-
【Web API系列教程】3.3 — 实战:处理数据(建立数据库)
-
【Web API系列教程】3.2 — 实战:处理数据(添加模型和控制器)
-
【Web API系列教程】3.10 — 实战:处理数据(发布App到Azure App Service)