Reading related data¶
The Contoso University sample web application demonstrates how to create ASP.NET Core 1.0 MVC web applications using Entity Framework Core 1.0 and Visual Studio 2015. For information about the tutorial series, see the first tutorial in the series.
In the previous tutorial you completed the School data model. In this tutorial you’ll read and display related data – that is, data that the Entity Framework loads into navigation properties.
在前面的教程中,已经完成了关于School的数据模型。在本教程中,将实现对关系数据的读取和显示,关系数据是的就是通过EF加载的导航属性数据。
The following illustrations show the pages that you’ll work with.
Sections:
-
Explicit loading. When the entity is first read, related data isn’t retrieved. You write code that retrieves the related data if it’s needed. As in the case of eager loading with separate queries, explicit loading results in multiple queries sent to the database. The difference is that with explicit loading, the code specifies the navigation properties to be loaded. Entity Framework Core 1.0 does not provide an explicit loading API.
- 显式加载。当第一次读取实体时,没有取回关系数据。在需要时,可编写取回关系数据的代码。因为在使用分步查询进行预加载时,显示加载的结果是向数据库发送了复合查询。区别是,使用显示加载时指定导航属性的代码将被加载。EF Core 1.0不提供显示加载的API。
-
Lazy loading. When the entity is first read, related data isn’t retrieved. However, the first time you attempt to access a navigation property, the data required for that navigation property is automatically retrieved. A query is sent to the database each time you try to get data from a navigation property for the first time. Entity Framework Core 1.0 does not support lazy loading.
- 延迟加载。当首次读取实体时,没有取回关系数据。然而,你第一次尝试连接导航属性时,自动取回导航属性所需的数据。第一次尝试从导航属性取得数据时,每次都向数据库发送一个查询。EF Core 1.0不支持延迟加载。
Performance considerations¶ 对性能的考虑
If you know you need related data for every entity retrieved, eager loading often offers the best performance, because a single query sent to the database is typically more efficient than separate queries for each entity retrieved. For example, suppose that each department has ten related courses. Eager loading of all related data would result in just a single (join) query and a single round trip to the database. A separate query for courses for each department would result in eleven round trips to the database. The extra round trips to the database are especially detrimental to performance when latency is high.
如果你知道每一个取回的实体都需要关系数据,那么预加载通常提供了最好的性能,因为发到数据库的单独查询通常情况下比分步查询更有效率。例如,假定每个系有10个关系课程,预加载所有的关系数据会导致仅有一个单独的(join)查询,这样就会在数据库中进行一次循环。对每个系的课程执行分步查询将导致数据库中11次循环。当潜在因素较高时,数据库额外的循环特别影响性能。
On the other hand, in some scenarios separate queries is more efficient. Eager loading of all related data in one query might cause a very complex join to be generated, which SQL Server can’t process efficiently. Or if you need to access an entity’s navigation properties only for a subset of a set of the entities you’re processing, separate queries might perform better because eager loading of everything up front would retrieve more data than you need. If performance is critical, it’s best to test performance both ways in order to make the best choice.
另一方面,在某些场景下分步查询会更有效率。在一条查询中预加载所有的关系数据可能导致产生非常复杂的join语句,这使得SQL Server不能高效地执行。或者,如果仅需要使用集合的一个子集的实体导航实行,分步查询可能执行起来效率更高,因为预加载所有的数据会返回比你需要的更多的数据。如果查询的性能非常重要,最好测试这两种方法以便做出正确的选择。
Create a Courses page that displays Department name¶ 新建显示系名称的课程页面
The Course entity includes a navigation property that contains the Department entity of the department that the course is assigned to. To display the name of the assigned department in a list of courses, you need to get the Name property from the Department entity that is in the Course.Department
navigation property.
设计时,课程实体包含了系实体的导航属性。要在课程列表中显示指定的系名称,需要取得Course.Department导航属性连接的系实体相关的Name属性。
Create a controller named CoursesController for the Course entity type, using the same options for the MVC Controller with views, using Entity Framework scaffolder that you did earlier for the Students controller, as shown in the following illustration:
为课程实体类型新建一个名为CoursesController的控制器,使用MVC Controller with views, 使用EF基架生成器,如下图所示:
Open CourseController.cs and examine the Index
method. The automatic scaffolding has specified eager loading for the Department
navigation property by using the Include
method.
Replace the Index
method with the following code that uses a more appropriate name for the IQueryable
that returns Course entities (courses
instead of schoolContext
):
打开CourseController.cs文件并对Index方法进行测试。自动搭建的结构为Department导航属性指定了预加载模式(使用Include方法)。用下列代码替换Index方法,使用更恰当的IQueryable返回课程实体(使用courses而不是schoolContext):
public async Task<IActionResult> Index() { var courses = _context.Courses .Include(c => c.Department) .AsNoTracking(); return View(await courses.ToListAsync()); }
Open Views/Courses/Index.cshtml and replace the template code with the following code. The changes are highlighted:
@model IEnumerable<ContosoUniversity.Models.Course> @{ ViewData["Title"] = "Courses"; } <h2>Courses</h2> <p> <a asp-action="Create">Create New</a> </p> <table class="table"> <thead> <tr> <th> @Html.DisplayNameFor(model => model.CourseID) </th> <th> @Html.DisplayNameFor(model => model.Credits) </th> <th> @Html.DisplayNameFor(model => model.Title) </th> <th> @Html.DisplayNameFor(model => model.Department) </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.CourseID) </td> <td> @Html.DisplayFor(modelItem => item.Credits) </td> <td> @Html.DisplayFor(modelItem => item.Title) </td> <td> @Html.DisplayFor(modelItem => item.Department.Name) </td> <td> <a asp-action="Edit" asp-route-id="@item.CourseID">Edit</a> | <a asp-action="Details" asp-route-id="@item.CourseID">Details</a> | <a asp-action="Delete" asp-route-id="@item.CourseID">Delete</a> </td> </tr> } </tbody> </table>
You’ve made the following changes to the scaffolded code:
你已经对自动生成的框架做了如下改动:
-
Changed the heading from Index to Courses.
- 改变了Course的Index页面的标头
-
Added a Number column that shows the
CourseID
property value. By default, primary keys aren’t scaffolded because normally they are meaningless to end users. However, in this case the primary key is meaningful and you want to show it. - 添加一个Number列来显示CourseID属性的值。默认情况下,自动生成的代码不包含主键,因为使用起来通常主键是没有意义的。然而,在这种情况下主键是有意义的,并且你想将其展现出来。
-
Added the Department column. Notice that for the Department column, the code displays the
Name
property of the Department entity that’s loaded into theDepartment
navigation property: -
添加了Department列。注意Department列,显示系实体的Name属性的代码,它加载到Department导航属性中:
@Html.DisplayFor(modelItem => item.Department.Name)
Run the page (select the Courses tab on the Contoso University home page) to see the list with department names.
运行该页面(在Home页面上选择Courses)查看带有系名的列表。
Create an Instructors page that shows Courses and Enrollments¶ 新建Instructors页面显示课程和注册信息
In this section you’ll create a controller and view for the Instructor entity in order to display the Instructors page:
在该节中,你将新建Instructor实体的控制器和视图,用来显示Instrctors页面:
This page reads and displays related data in the following ways:
该页面通过以下方法读取、显示关系数据:
- The list of instructors displays related data from the OfficeAssignment entity. The Instructor and OfficeAssignment entities are in a one-to-zero-or-one relationship. You’ll use eager loading for the OfficeAssignment entities. As explained earlier, eager loading is typically more efficient when you need the related data for all retrieved rows of the primary table. In this case, you want to display office assignments for all displayed instructors.
- Instuctors列表显示了来自OfficeAssignment实体的关系数据。Instructor和OfficeAssignment实体之间有1-0-或-1的关系。你将预加载OfficeAssignment实体。先解释一下,当需要取回主表的所有行时,预加载通常更为高效。在这里,你意图显示所有的讲师及其办公室安排。
- When the user selects an instructor, related Course entities are displayed. The Instructor and Course entities are in a many-to-many relationship. You’ll use eager loading for the Course entities and their related Department entities. In this case, separate queries might be more efficient because you need courses only for the selected instructor. However, this example shows how to use eager loading for navigation properties within entities that are themselves in navigation properties.
- 当用户选择一个讲师时就会显示相关的课程实体。讲师和课程实体具有多对多的关系。你将使用预加载处理课程实体及其相关的系实体。在这种情况下,分步查询可能更高效,因为你仅需要被选择讲师的课程信息。然而,该例子展示了如何使用预加载的方式处理实体中的导航属性,that are themselves in navigation properties???
- When the user selects a course, related data from the Enrollments entity set is displayed. The Course and Enrollment entities are in a one-to-many relationship. You’ll use separate queries for Enrollment entities and their related Student entities.
- 当用户选择一门课程时,就会显示来自注册实体集合中的关系数据。课程和注册实体具有1对多的关系。你将使用分步查询来处理注册实体机器相关的学生实体。
Create a view model for the Instructor Index view¶ 为Instructor Index视图新建视图模型
The Instructors page shows data from three different tables. Therefore, you’ll create a view model that includes three properties, each holding the data for one of the tables.
In the SchoolViewModels folder, create InstructorIndexData.cs and replace the existing code with the following code:
讲师页面显示的数据来自于三个不同的表。因此,你将新建一个视图模型,其包含三个属性,每个属性控制一个表的数据。
在SchoolViewModels文件夹中,新建一个文件InstructorIndexData.cs,并用下列代码替换原有代码:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace ContosoUniversity.Models.SchoolViewModels { public class InstructorIndexData { public IEnumerable<Instructor> Instructors { get; set; } public IEnumerable<Course> Courses { get; set; } public IEnumerable<Enrollment> Enrollments { get; set; } } }
Create the Instructor controller and views¶ 新建讲师控制器和视图
Create an Instructors controller with EF read/write actions as shown in the following illustration:
用EF read/write动作新建讲师控制器,如下图所示:
Open InstructorsController.cs and add a using statement for the ViewModels namespace:
打开InstructorsController.cs,并且添加using语句:
using ContosoUniversity.Models.SchoolViewModels;
Replace the Index method with the following code to do eager loading of related data and put it in the view model.
用下列代码替换Index方法,以实现关系数据的预加载,并将其放置于视图模型内。
public async Task<IActionResult> Index(int? id, int? courseID) { var viewModel = new InstructorIndexData(); viewModel.Instructors = await _context.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses) .ThenInclude(i => i.Course) .ThenInclude(i => i.Enrollments) .ThenInclude(i => i.Student) .Include(i => i.Courses) .ThenInclude(i => i.Course) .ThenInclude(i => i.Department) .AsNoTracking() .OrderBy(i => i.LastName) .ToListAsync(); if (id != null) { ViewData["InstructorID"] = id.Value; Instructor instructor = viewModel.Instructors.Where( i => i.ID == id.Value).Single(); viewModel.Courses = instructor.Courses.Select(s => s.Course); } if (courseID != null) { ViewData["CourseID"] = courseID.Value; viewModel.Enrollments = viewModel.Courses.Where( x => x.CourseID == courseID).Single().Enrollments; } return View(viewModel); }
The method accepts optional route data (id
) and a query string parameter (courseID
) that provide the ID values of the selected instructor and selected course. The parameters are provided by the Select hyperlinks on the page.
该方法接收了可选的路由数据(id)以及一个查询字符串参数(courseID),该字符串提供了被选择讲师和课程的ID值。该参数通过页面上的Select链接提供。
The code begins by creating an instance of the view model and putting in it the list of instructors. The code specifies eager loading for the Instructor.OfficeAssignment
and the Instructor.Courses
navigation property. Within the Courses
property, the Enrollments
and Department
properties are loaded, and within each Enrollment
entity the Student
property is loaded.
代码开始新建了视图模型实例,并将一个讲师列表放入其中。代码指定预加载Instructor.OfficeAssignment
和Instructor.Courses 导航属性。在Courses 属性中,加载了Enrollments
和Department属性,同时每个
Enrollment实体中的
Student属性也进行了加载。
viewModel.Instructors = await _context.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses) .ThenInclude(i => i.Course) .ThenInclude(i => i.Enrollments) .ThenInclude(i => i.Student) .Include(i => i.Courses) .ThenInclude(i => i.Course) .ThenInclude(i => i.Department) .AsNoTracking() .OrderBy(i => i.LastName) .ToListAsync();
Since the view always requires the OfficeAssignment entity, it’s more efficient to fetch that in the same query. Course entities are required when an instructor is selected in the web page, so a single query is better than multiple queries only if the page is displayed more often with a course selected than without.
因为视图总是需要OfficeAssignment实体,所以在同一个查询中获取这些信息更为高效。当在页面中选择某个讲师时就会需要课程实体,所以只有在页面经常显示一个被选择的课程时,使用单独的查询比复合查询更好。
If an instructor was selected, the selected instructor is retrieved from the list of instructors in the view model. The view model’s Courses
property is then loaded with the Course entities from that instructor’s Courses
navigation property.
如果选择了一个讲师,就会从视图模型的讲师列表中返回该讲师。视图模型中的Course属性接下来就通过讲师实体的Course导航属性加载了。
if (id != null) { ViewData["InstructorID"] = id.Value; Instructor instructor = viewModel.Instructors.Where( i => i.ID == id.Value).Single(); viewModel.Courses = instructor.Courses.Select(s => s.Course); }
The Where
method returns a collection, but in this case the criteria passed to that method result in only a single Instructor entity being returned. The Single
method converts the collection into a single Instructor entity, which gives you access to that entity’s Courses
property. The Courses
property contains CourseInstructor
entities, from which you want only the related Course entities.
Where方法返回了一个集合,但这里的规则是:传递给该方法的结果只需要一个单个的讲师。Single方法将集合转化为一个单独的讲师实体,该实体可使你连接那个实体的Courses属性。Courses属性包含了CourseInstructor实体,从该处你只想得到Course实体的关系数据。
You use the Single
method on a collection when you know the collection will have only one item. The Single method throws an exception if the collection passed to it is empty or if there’s more than one item. An alternative is SingleOrDefault
, which returns a default value (null in this case) if the collection is empty. However, in this case that would still result in an exception (from trying to find a Courses
property on a null reference), and the exception message would less clearly indicate the cause of the problem. When you call the Single
method, you can also pass in the Where condition instead of calling the Where
method separately:
当知道一个集合仅有一条记录时,在集合后使用Single方法。如果集合传递给它的是空值,或者有超过一条以上的记录,Single方法就会抛出一个异常。一个相关的方法是SingleOrDefault,如果集合是空集,该方法返回一个默认值(在这里是null)。然而,在这里仍然会导致一个异常(来自试图从一个null引用上寻找一个Courses实体),并且异常信息反应的问题原因不太清楚。当你调用Single方法时,你也可传递一个Where条件,而不是单独地调用Where方法:
.Single(i => i.ID == id.Value)
Instead of: 替代:
.Where(I => i.ID == id.Value).Single()
Next, if a course was selected, the selected course is retrieved from the list of courses in the view model. Then the view model’s Enrollments
property is loaded with the Enrollment entities from that course’s Enrollments
navigation property.
接下来,如果选择了一个课程,就会从视图模型中的课程列表中返回该被选课程。然后,就加载了视图模型中的Enrollments属性,同时也就从该课程的Enrollments导航属性加载了注册实体。
if (courseID != null) { ViewData["CourseID"] = courseID.Value; viewModel.Enrollments = viewModel.Courses.Where( x => x.CourseID == courseID).Single().Enrollments; }
Modify the Instructor Index view¶ 修改讲师Index视图
In Views/Instructor/Index.cshtml, replace the template code with the following code. The changes (other than column reordering)are highlighted.注意高亮的修改部分:
@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData @{ ViewData["Title"] = "Instructors"; } <h2>Instructors</h2> <p> <a asp-action="Create">Create New</a> </p> <table class="table"> <thead> <tr> <th>Last Name</th> <th>First Name</th> <th>Hire Date</th> <th>Office</th> <th>Courses</th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model.Instructors) { string selectedRow = ""; if (item.ID == (int?)ViewData["InstructorID"]) { selectedRow = "success"; } <tr class="@selectedRow"> <td> @Html.DisplayFor(modelItem => item.LastName) </td> <td> @Html.DisplayFor(modelItem => item.FirstMidName) </td> <td> @Html.DisplayFor(modelItem => item.HireDate) </td> <td> @if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location } </td> <td> @{ foreach (var course in item.Courses) { @course.Course.CourseID @: @course.Course.Title <br /> } } </td> <td> <a asp-action="Index" asp-route-id="@item.ID">Select</a> | <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> | <a asp-action="Details" asp-route-id="@item.ID">Details</a> | <a asp-action="Delete" asp-route-id="@item.ID">Delete</a> </td> </tr> } </tbody> </table>
You’ve made the following changes to the existing code: 已将代码做了如下变化:
-
Changed the model class to
InstructorIndexData
. 将模型类变更为InstructorIndexData。 -
Changed the page title from Index to Instructors. 将页首标题从Index变更为Instructors。
-
Added an Office column that displays
item.OfficeAssignment.Location
only ifitem.OfficeAssignment
is not null. (Because this is a one-to-zero-or-one relationship, there might not be a related OfficeAssignment entity.) 增加了Office列,如果item.OfficeAssignment 非空的情况下,会显示item.OfficeAssignment.Location ,(因为是1-0或-1的关系,有可能不是一个关系的OfficeAssignment实体)@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
-
Added a Courses column that displays courses taught by each instructor. 增加了Course列,用于显示每个讲师讲授的课程。
-
Added code that dynamically adds
class="success"
to thetr
element of the selected instructor. This sets a background color for the selected row using a Bootstrap class. 增加了代码,动态向tr标签添加被选讲师的class=“success”。string selectedRow = ""; if (item.ID == (int?)ViewData["InstructorID"]) { selectedRow = "success"; }
-
Added a new hyperlink labeled Select immediately before the other links in each row, which causes the selected instructor’s ID to be sent to the
Index
method. 在每行其他链接后面增加了一个新链接标签Select,用于将被选讲师的ID传递给Index方法。<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
-
Reordered the columns to display Last Name, First Name, Hire Date, and Office in that order. 记录的列按照Last Name, First Name, Hire Date, Office的顺序进行显示。
Run the application and select the Instructors tab. The page displays the Location property of related OfficeAssignment entities and an empty table cell when there’s no related OfficeAssignment entity.
运行运用并选择讲师。页面会就显示相关OfficeAssignment实体的属性,如果没有相关的OfficeAssignment实体就会显示一个空表。
In the Views/Instructor/Index.cshtml file, after the closing table element (at the end of the file), add the following code. This code displays a list of courses related to an instructor when an instructor is selected.
在Views/Instructor/Index.cshtml文件中,在关闭表格的标签之后(在文件结尾处),添加下列代码。当选择了一个教师时,该代码显示一个相关的课程列表。
@if (Model.Courses != null) { <h3>Courses Taught by Selected Instructor</h3> <table class="table"> <tr> <th></th> <th>Number</th> <th>Title</th> <th>Department</th> </tr> @foreach (var item in Model.Courses) { string selectedRow = ""; if (item.CourseID == (int?)ViewData["CourseID"]) { selectedRow = "success"; } <tr class="@selectedRow"> <td> @Html.ActionLink("Select", "Index", new { courseID = item.CourseID }) </td> <td> @item.CourseID </td> <td> @item.Title </td> <td> @item.Department.Name </td> </tr> } </table> }
This code reads the Courses
property of the view model to display a list of courses. It also provides a Select hyperlink that sends the ID of the selected course to the Index
action method.
该部分代码读取视图模型的Courses属性进而显示课程列表,同时也提供了Select链接,向Index方法发送被选课程的ID。
Run the page and select an instructor. Now you see a grid that displays courses assigned to the selected instructor, and for each course you see the name of the assigned department.
运行该页面并选择一个讲师。现在你会看到一个表格,显示了被选讲师的课程,你也会看到每个课程相关的系的名称。
After the code block you just added, add the following code. This displays a list of the students who are enrolled in a course when that course is selected.
在刚刚添加的代码块后,添加如下代码。当选择课程时,就会显示已注册该课程的学生列表。
@if (Model.Enrollments != null) { <h3> Students Enrolled in Selected Course </h3> <table class="table"> <tr> <th>Name</th> <th>Grade</th> </tr> @foreach (var item in Model.Enrollments) { <tr> <td> @item.Student.FullName </td> <td> @Html.DisplayFor(modelItem => item.Grade) </td> </tr> } </table> }
This code reads the Enrollments property of the view model in order to display a list of students enrolled in the course.
改代码读取视图模型的Enrollments属性,用于显示已注册该课程的学生列表。
Run the page and select an instructor. Then select a course to see the list of enrolled students and their grades.
运行该页面并选择一个讲师。然后选择一门课程,看一下已注册学生的列表和相应的班级。
Use multiple queries¶ 使用复合查询
When you retrieved the list of instructors in InstructorsController.cs, you specified eager loading for the Courses
navigation property.
在InstructorsController.cs文件中,当你取回讲师列表时,你为Course导航属性指定了预加载。
Suppose you expected users to only rarely want to see enrollments in a selected instructor and course. In that case, you might want to load the enrollment data only if it’s requested. To do that you (a) omit eager loading for enrollments when reading instructors, and (b) only when enrollments are needed, call the Load
method on an IQueryable
that reads the ones you need (starting in EF Core 1.0.1, you can use LoadAsync
). EF automatically “fixes up” the Courses
navigation property of already-retrieved Instructor entities with data retrieved by the Load
method.
假定你希望用户在选择的讲师和课程中很少想看到注册信息。在这种情况下,你可能想仅在发出请求时才加载注册日期。为了实现这个目的,你会这样做:
首先:当读取讲师信息时忽略注册信息的预读取;
其次:仅当需要注册信息时,再以IQueryable方式调用Load方法读取需要的一个数据(从EF Core 1.0.1开始,可以使用LoadAsync了)。EF自动“修复”经Load方法已取回的讲师实体数据包含的Course导航属性。
To see this in action, replace the Index
method with the following code:
要看一下该方法,用下列代码替换Index方法:
public async Task<IActionResult> Index(int? id, int? courseID) { var viewModel = new InstructorIndexData(); viewModel.Instructors = await _context.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses) .ThenInclude(i => i.Course) .Include(i => i.Courses) .ThenInclude(i => i.Course) .ThenInclude(i => i.Department) .OrderBy(i => i.LastName) .ToListAsync(); if (id != null) { ViewData["InstructorID"] = id.Value; Instructor instructor = viewModel.Instructors.Where( i => i.ID == id.Value).Single(); viewModel.Courses = instructor.Courses.Select(s => s.Course); } if (courseID != null) { ViewData["CourseID"] = courseID.Value; _context.Enrollments .Include(i => i.Student) .Where(c => c.CourseID == courseID.Value).Load(); viewModel.Enrollments = viewModel.Courses.Where( x => x.CourseID == courseID).Single().Enrollments; } return View(viewModel); }
The new code drops the ThenInclude method calls for enrollment data from the code that retrieves instructor entities. If an instructor and course are selected, the highlighted code retrieves Enrollment entities for the selected course. With these Enrollment entities, the code eagerly loads the Student navigation property.
新代码舍弃了ThenInclude方法,而是从已返回的讲师实体集合的代码中调用注册数据。如果选择了一个讲师和课程,高亮代码取回被选课程的注册实体信息。对于这些注册实体,代码预加载了学生的导航属性。
So now, only enrollments taught by the selected instructor in the selected course are retrieved from the database.
所以现在,从数据库中取回的只是那些在被选课程中的被选讲师的讲授注册信息。
Notice that the original query on the Instructors entity set now omits the AsNoTracking
method call. Entities must be tracked for EF to “fix up” navigation properties when you call the Load
method.
注意对讲师实体集合的原始查询现在已经忽略了调用AsNoTarcking方法。对EF来说,调用Load方法时要“修复”导航属性,实体必须可被跟踪。
Run the Instructor Index page now and you’ll see no difference in what’s displayed on the page, although you’ve changed how the data is retrieved.
运行讲师的Index页面,你将看到页面上的显示没有什么不同,尽管已经改变了如何取回这些数据的方法。