对于orm框架而言最显著的一个特征就是延迟加载(lazy laoding),自然entity framework 也不例外,ms的entity frame work 是一个新的强大的数据模型工具,除了orm功能之外,还有其它更多的功能。因此对于了解entity framework是怎样通过不同的方式来体现这一特征,显得十分必要。本文将告诉我们EF在设计时那些所不为人知的:为什么EF不同于那些你所使用过的以及EF是如何实现你所期待的延迟加载这一特征的。认识到ORM并不是一个新的概念,这很重要。有许多优秀的为Ruby ,Python,甚至也有为.net framework (Nhibernate)的ORM框架出现。正是如此,当你关注EF时,你可能陷入一个和我类似的的困惑(延迟加载的方式并不是我想要的)。
于orm框架而言最显著的一个特征就是延迟加载(lazy laoding),自然entity framework 也不例外,ms的entity frame work 是一个新的强大的数据模型工具,除了orm功能之外,还有其它更多的功能。因此对于了解entity framework是怎样通过不同的方式来体现这一特征,显得十分必要。本文将告诉我们EF在设计时那些所不为人知的:为什么EF不同于那些你所使用过的以及EF是如何实现你所期待的延迟加载这一特征的。认识到ORM并不是一个新的概念,这很重要。有许多优秀的为Ruby ,Python,甚至也有为.net framework (Nhibernate)的ORM框架出现。正是如此,当你关注EF时,你可能陷入一个和我类似的的困惑(延迟加载的方式并不是我想要的)。因为我使用过LINQ to SQL,我非常期待隐式的延迟加载,并且因此EF将会打破这一界限,后来才知道这是错误的。在之前我详细的描述过entity framework 在设计上与Linq to SQL 的不同(或其它的ORM框架像Nhibernate),我想通过code来展示这些,场景非常简单,在数据库里有2张表"Customers" 表和"Orders"表。我用过LINQ to SQL设计器和Entity framework 设计器,这2个设计器都很容易的产生的C# 代码并与我的数据库交互。我的sql 表只有几条数据,Customers只有1条数据,Orders有3条数据是与Customer相关联,
Code
1 // First, using LINQ to SQL
2 L2SDataContext linqToSqlContext = new L2SDataContext();
3 this.MyDataGrid1.DataSource = linqToSqlContext.Customers.First().Orders;
4 // 3 records in my data grid!!!
5 this.MyDataGrid1.DataBind();
上面的代码完成的工作正如我期待的。第一个Customer对象从数据库取出,并且我随后就访问那个Customer's 的Orders.LINQ to SQL 会返回到数据库取出Orders。现在下面是我用Entity Framework来做同样的测试,注意有多少行数据在我的datagrid:
Code
1 // Using the Entity Framework
2 EFEntities entityFrameworkContext = new EFEntities();
3 this.MyDataGrid2.DataSource = entityFrameworkContext.Customers.First().Orders;
4 // 0 records in my data grid
5 this.MyDataGrid2.DataBind();
就像上面的例子一样,有时候有些事情犹豫太简单以至我陷入混淆状态,实际上这里暴露出的问题也是为什么EF一出来就遭到一部分人的放弃,这个问题其实并不是Entity framework带来的,而是由于缺乏对EF底层设计的理解。
为什么EF不延迟加载(Lazy Loading)呢?
正与我以前提到的,Entity Framework 提供 ORM功能(包含延迟加载)。实际上,EF设计小组从来没有对外宣称将会遵从延迟加载的设计风格(Lazy Loading Design Pattern),取而代之的是:EF提供‘defferred’ Loading(延期加载)功能。在另一个角度来讲,延迟加载(lazy loading),延迟初始化(lazy initialization),延期加载deffered loading,需要时加载(on-demand loading)和 即使加载(just-in-time loading)所有这些加载方式都意味着同一个意思。
在上面的例子中,LINQ to SQL 自动去数据库为第一个Customers加载Orders,可是EF的设计小组并不希望这种自动发生的行为,背后的原因其实很简单:在一个大型的项目中,对于开发人员而言非常清晰地知道何时他们会访问特定的资源(如数据库)是非常重要的。这样的后果就是,需要一个显式的“.Load()”方法加载对象。否则 你会急切(eagerly)的加载属性在一开始就使用“.Inculde”调用:
Code
1 // Explicitly include the orders from the database in one shot.
2 this.MyDataGrid2.DataSource = entityFrameworkContext.Customers
3 .Include("Orders").First().Orders;
4 // Or you can
5 // Explicitly load the orders after retrieving the customer.
6 var customer = entityFrameworkContext.Customers.First();
7 customer.Orders.Load();
8 this.MyDataGrid2.DataSource = customer.Orders;
首先,我非常不喜欢这种设计风格,然而在我和一些天才(Julie Lerman, Elisa Flasko - MSFT and Jonathan Carter - MSFT就这个问题探讨之后,我才知道这个决定之后所展现出的智慧。尽管上面的理由是很好,但他们不能满足所有的设计场景,这里有一个例子在这里你将需要自动延迟加载,并且我将告诉你们在EF中怎样实现这种功能。以这个场景作为一个例子, 我的一个开发小组是关于Sharepoint的项目,结果要求是一个可配置的web应用程序并且用户可以根据自己的需要添加和删除web Parts,我们进行如下假设:
1.开发小组的负责人创建了一个静态类"BusinessObjects",暴露了许多属性,其中之一是"CurrenctCustomer".
2.Web Part "CustomerBasicInfo" 将显示"CurrentCustomer"的名字。
3.Web Part "CustomerAvailableAddresses"将会卡片上的Customer显示一个地址列表。
4.Web Part "CustomerBillingHistory" 将会显示一个Grid的所有Orders.
在这个场景中"BusinessObjects.CurrentCustomer"的属性不要急切加载".Addresses" and ".Orders" 的Customer对象,如果用户不需要这个Web Part出现在它的画面,但是这就会浪费带宽去下载数据。上面那部分文章,是Timothy Khouri的一篇文章的一部分,个人觉得写的非常棒,就把它翻译了。虽然我没有使用过Linq to sql。 不过的确看到不少E文里抱怨Linq to sql 的隐式加载带来的麻烦。先看一个简单的例子(还是前几篇关于继承的模型)
我要通过entitydatasource给gridview配置数据源来访问上面的模型
Code
1 <dxwgv:ASPxGridView ID="ASPxGridView1" runat="server"
2 DataSourceID="EntityDataSource1" AutoGenerateColumns="False"
3 KeyFieldName="PersonID">
4 <Columns>
5 <dxwgv:GridViewDataTextColumn FieldName="PersonID" ReadOnly="True"
6 VisibleIndex="0">
7 </dxwgv:GridViewDataTextColumn>
8 <dxwgv:GridViewDataTextColumn FieldName="Name" VisibleIndex="1">
9 </dxwgv:GridViewDataTextColumn>
10 <dxwgv:GridViewDataTextColumn FieldName="Age" VisibleIndex="2">
11 </dxwgv:GridViewDataTextColumn>
12 <dxwgv:GridViewDataTextColumn FieldName="Country" VisibleIndex="3">
13 </dxwgv:GridViewDataTextColumn>
14 <dxwgv:GridViewDataTextColumn FieldName="City" VisibleIndex="4">
15 </dxwgv:GridViewDataTextColumn>
16 <dxwgv:GridViewDataDateColumn FieldName="BirthDay" VisibleIndex="5">
17 </dxwgv:GridViewDataDateColumn>
18 <dxwgv:GridViewDataTextColumn FieldName="EmployeeNo" VisibleIndex="6">
19 </dxwgv:GridViewDataTextColumn>
20 <dxwgv:GridViewDataTextColumn FieldName="TypeOfRank.text" VisibleIndex="7">
21 </dxwgv:GridViewDataTextColumn>
22 <dxwgv:GridViewDataTextColumn FieldName="TypeOfDepartment.text"
23 VisibleIndex="8">
24 </dxwgv:GridViewDataTextColumn>
25 </Columns>
26 </dxwgv:ASPxGridView>
27 <asp:EntityDataSource ID="EntityDataSource1" runat="server"
28 ConnectionString="name=testEntities" DefaultContainerName="testEntities"
29 EnableDelete="True" EnableInsert="True" EnableUpdate="True"
30 EntitySetName="Perosn" EntityTypeFilter="employeeTest" Include="TypeOfRank,TypeOfDepartment">
31 </asp:EntityDataSource>
我通过Include实现对TypeOfRank,TypeOfDepartment的显式加载,。我们看一下entitydatasource生成的sql
Code
1SELECT
2[Project1].[C4] AS [C1],
3[Project1].[C1] AS [C2],
4[Project1].[EmployeeID] AS [EmployeeID],
5[Project1].[Name] AS [Name],
6[Project1].[Age] AS [Age],
7[Project1].[Country] AS [Country],
8[Project1].[City] AS [City],
9[Project1].[BirthDay] AS [BirthDay],
10[Project1].[EmployeeNo] AS [EmployeeNo],
11[Project1].[C2] AS [C3],
12[Project1].[code] AS [code],
13[Project1].[text] AS [text],
14[Project1].[value] AS [value],
15[Project1].[C3] AS [C4],
16[Project1].[code1] AS [code1],
17[Project1].[text1] AS [text1],
18[Project1].[value1] AS [value1]
19FROM ( SELECT
20 [Extent1].[EmployeeID] AS [EmployeeID],
21 [Extent1].[EmployeeNo] AS [EmployeeNo],
22 [Extent2].[Name] AS [Name],
23 [Extent2].[Age] AS [Age],
24 [Extent2].[Country] AS [Country],
25 [Extent2].[City] AS [City],
26 [Extent2].[BirthDay] AS [BirthDay],
27 '0X0X' AS [C1],
28 [Extent3].[code] AS [code],
29 [Extent3].[text] AS [text],
30 [Extent3].[value] AS [value],
31 CASE WHEN ([Extent3].[code] IS NULL) THEN CAST(NULL AS varchar(1)) ELSE '3X0X' END AS [C2],
32 [Extent4].[code] AS [code1],
33 [Extent4].[text] AS [text1],
34 [Extent4].[value] AS [value1],
35 CASE WHEN ([Extent4].[code] IS NULL) THEN CAST(NULL AS varchar(1)) ELSE '3X1X' END AS [C3],
36 1 AS [C4]
37 FROM [dbo].[employeeTest] AS [Extent1]
38 INNER JOIN [dbo].[Perosn] AS [Extent2] ON [Extent1].[EmployeeID] = [Extent2].[PersonID]
39 LEFT OUTER JOIN [dbo].[PickList] AS [Extent3] ON ([Extent3].[type] = 'Rank') AND ([Extent1].[Rank] = [Extent3].[code])
40 LEFT OUTER JOIN [dbo].[PickList] AS [Extent4] ON ([Extent4].[type] = 'Department') AND ([Extent1].[TypeOfDepartment] = [Extent4].[code])
41 WHERE ((CASE WHEN ([Extent1].[TypeOfEmployee] = 'Hour') THEN cast(1 as bit) ELSE cast(0 as bit) END) <> cast(1 as bit)) AND ((CASE WHEN ([Extent1].[TypeOfEmployee] = 'Month') THEN cast(1 as bit) ELSE cast(0 as bit) END) <> cast(1 as bit))
42) AS [Project1]
43ORDER BY [Project1].[EmployeeID] ASC
sql有些复杂,不过还是可以看出Person表与PickList表 left out join 了2次(因为2个关联对象)。如果不用 Include="TypeOfRank,TypeOfDepartment" 这句 sql:
Code
1SELECT
2[Project1].[C2] AS [C1],
3[Project1].[C1] AS [C2],
4[Project1].[EmployeeID] AS [EmployeeID],
5[Project1].[Name] AS [Name],
6[Project1].[Age] AS [Age],
7[Project1].[Country] AS [Country],
8[Project1].[City] AS [City],
9[Project1].[BirthDay] AS [BirthDay],
10[Project1].[EmployeeNo] AS [EmployeeNo],
11[Project1].[Rank] AS [Rank],
12[Project1].[TypeOfDepartment] AS [TypeOfDepartment]
13FROM ( SELECT
14 [Extent1].[EmployeeID] AS [EmployeeID],
15 [Extent1].[EmployeeNo] AS [EmployeeNo],
16 [Extent1].[TypeOfDepartment] AS [TypeOfDepartment],
17 [Extent1].[Rank] AS [Rank],
18 [Extent2].[Name] AS [Name],
19 [Extent2].[Age] AS [Age],
20 [Extent2].[Country] AS [Country],
21 [Extent2].[City] AS [City],
22 [Extent2].[BirthDay] AS [BirthDay],
23 '0X0X' AS [C1],
24 1 AS [C2]
25 FROM [dbo].[employeeTest] AS [Extent1]
26 INNER JOIN [dbo].[Perosn] AS [Extent2] ON [Extent1].[EmployeeID] = [Extent2].[PersonID]
27 WHERE ((CASE WHEN ([Extent1].[TypeOfEmployee] = 'Hour') THEN cast(1 as bit) ELSE cast(0 as bit) END) <> cast(1 as bit)) AND ((CASE WHEN ([Extent1].[TypeOfEmployee] = 'Month') THEN cast(1 as bit) ELSE cast(0 as bit) END) <> cast(1 as bit))
28) AS [Project1]
29ORDER BY [Project1].[EmployeeID] ASC
少了2次表的join。在我正在进行的项目里,在实体数据模型中,负载很重的业务实体会多达20几个关联对象,如果EF采用Lazy Loading Design Pattern 可以想象,无论对该业务实体进行多简单的查询都会去sql都会自动去join其它的数据表20次 ,显然是不希望被看到的,也是不被允许的。在接下来的文章里我将继续介绍Entity framework 不 LazyLoading所引起的一些负面问题以及设计一些场景来实现 Eager Loading 。