你有一个LINQ查询,使用了类似这样的操作 group by,join,和where;你想使用Include()方法预先加载额外的实体。另外你想使用Code-First来管理数据访问。
解决方案
假设你有如图5-22所示的概念模型
图5-22 一个简单的包含Club和Event以及它们之间一对多关联的模型
在Visual Studio中添加一个名为Recipe7的控制台应用,并确保引用了实体框架6的库,NuGet可以很好的完成这个任务。在Reference目录上右键,并选择 Manage NeGet Packages(管理NeGet包),在Online页,定位并安装实体框架6的包。这样操作后,NeGet将下载,安装和配置实体框架6的库到你的项目中。
创建一个名为Club和Event的类,复制代码清单5-41中的属性到这个类中,创建实体Club和Event实体。
代码清单5-14. Club、Event 实体类
接下来,创建一个名为Recipe7Context的类,并将代码清单5-51中的代码添加到其中,并确保其派生到DbContext类。
为了与group by从句组合使用Include()方法,Include()方法必须放在针对父实体的过虑和分组操作之后。如代码清单5-17所示。
代码清单5-17 . 当父实体上应用过虑和分组时,Include()方法的正确位置。
代码清单5-17的输出如下:
原理
我们创建了一个俱乐部(Club)和三个Events(活动)实体对象。在查询中,我们获取New York 的俱乐部中的所有活动。按俱乐部分组,并查找日期中最早的活动。注意,LINQ扩展方法FirstOrDefault(),巧妙地嵌入到了Select投影操作中。然而变量events只是一个表达式,它还没有在数据库中执行任何操作。
接下来,我们凭借Include()方法预先加载关联实体Club对象的信息,我们将第一个LINQ查询变量,events,作为第二个LINQ查询的输入。这是LINQ组合查询的一个示例。将一个复杂的LINQ查询转换成一系列的短小查询。前面的查询变量是后面查询的数据源。
注意,我们使用First()方法只是为获取第一个Event实例,这样将返回一个Event类型,而不是Event对象的集合。实体框架6包含了一个新的名为 IQueryableExtensions的接口,它公布了Include()方法的原型,它能接受一个基于字符串或是强类型的查询路径作为参数。IQueryableExtensions替换了EF4和EF5中的DbExtension类。
很多开发人员觉得Include()方法很让人迷惑,在一些情况下,智能感知不能有效地提示(因为表达式类型)。在一些情况下,它会在运行时悄悄地被忽略掉。特别地,除非编译器无法确定其结果类型,否则不会给出警告或提示。很多问题都在运行时才会暴露出来,这会使问题更难解决。这里有一些使用Include()方法的准则:
1、Include()方法是一个在IQueryable<T>上的扩展方法;
2、Include()方法只能应用在最终的查询结果集上,当它被在subquery(子查询)、join(连接)或者嵌套从句中,当生成命令树时,它将被忽略掉。在幕后,实体框架会把你的LINQ to Entites查询转换成一棵命令树,然后数据库提供者(database provider)将其处理并构建成一个用于数据库的SQL查询(译注:这一点很重要,我在上面吃过亏,直到现在才弄明白)
3、Include()方法只能应用 在实体类型的结果集上,如果表达式将结果投影到一个非实体类型的类型上,Include()方法将被忽略。
4、在Include()方法和最外面的操作之间,不能改变结果集的类型。例如,一个group by从句,改变结果集的类型。
5、用于Include()方法的查询路径表达式必须从最外层操作返回的类型中的导航属性开始,查询路径不能从任意点开始。
问题
你有一个实体的实例,你想在一个单独的查询中延缓加载其中两个或多个关联实体。这里尤其重要的是,我们如何使用Load()方法来避免查询相同的对象两次。另外你想使用Code-First来管理数据访问。
解决方案
假设你有如图5-23所示的概念模型.
图5-23 一个包含职员(employee),他的部门(department)和部门所在的公司(company)的模型
在Visual Studio中添加一个名为Recipe8的控制台应用,并确保引用了实体框架6的库,NuGet可以很好的完成这个任务。在Reference目录上右键,并选择 Manage NeGet Packages(管理NeGet包),在Online页,定位并安装实体框架6的包。这样操作后,NeGet将下载,安装和配置实体框架6的库到你的项目中。
接下来我们创建三个实体对象:Company,Departmet和Employee,复制代码清单5-18中的属性到这三个类中。
代码清单5-18. 实体类
接下来,创建一个名为Recipe8Context的类,并将代码清单5-19中的代码添加到其中,并确保其派生到DbContext类。
在图5-23所示的模型中,一个职员(Employee)被关联到一个确切的部门(Department)。每个部门被关联到一个确切公司(Company)。
给定一个Employee的实例,你想加载他的部门以及部门所在的公司。是什么让这个问题变得有点特别呢? 我们已经有了一个Employee的实例,我们想避免再一次到数据库中获取Emplyee对象的副本,这种情况,我们可以使用Include()方法来获取关联实体Company和Department。也许,在真实的情况中,Employee的获取和实例化需要非常高的代价。
我们可以使用Load()方法,两次去加载关联实体,一次加载Department实例,一次去加载Company实例。然而,这会产生两次数据库交互。为了在一个查询中加载关联实体的实例,我们可以使用Include()方法和包含Department,Company查询路径,重新在Emlpoyee实体集上查询。或者组合使用Reference()和Query()方法。代码清单5-21演示了这些方法。
代码清单5-21.将数据插入到模型并使用两种稍有不同的方法加载关联实体
代码清单5-21的输出如下:
原理
在第二个查询中,我们使用上下文对象DbContext公布的Entry()方法访问Employee对象并对其执行操作。然后我们链式调用Reference()方法和DbReferenceEntity类的Query()方法,返回一个从数据库中加载关联对象Deparment的查询。另外,我们链式调用Include()方法来拉取关联对象Company的信息。正如所期望的那样,这个查询获取了Department和Company的数据,它没有去获取Employees的数据,因为这些数据已经存在于上下文对象中了。