《Entity Framework 6 Recipes》中文翻译系列 (34) ------ 第六章 继承与建模高级应用之多条件与QueryView
翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇
6-10 创建一个多条件过滤
问题
你想使用多个条件为实体过滤表中的行。
解决方案
假设你有一张处理网站订单的表,如图6-13所示。
图6-13 表WebOrder包含网站订单的信息
假设我们有这样一个业务需求,WebOrder中的实例为,2012年以后的,2010年到2012年之间未删除的,2010年以前的订单金额大于200美元的。这样的复杂过滤条件不能使用映射详细信息窗口中有限制的条件来创建了。 有一种实现方法是使用QueryView。按下面的步骤,使用QueryView来为这个实体和满足业务需要的条件建模:
1、在你的项目中添加一个ADO.NET Entity Data Model(ADO.NET实体数据模型),并导入表WebOrder。使用代码清单6-27中的代码创建存储过程。接下来的两步,我们将为WebOrder实体映射insert,update和delete动作。
代码清单6-27. 在数据库中为WebOrder实体定义的Insert,Update和Delete动作
1 create procedure [Chapter6].[InsertOrder] 2 (@CustomerName varchar(50),@OrderDate date,@IsDeleted bit,@Amount decimal) 3 as 4 begin 5 insert into chapter6.WebOrder (CustomerName, OrderDate, IsDeleted, Amount) 6 values (@CustomerName, @OrderDate, @IsDeleted, @Amount) 7 select SCOPE_IDENTITY() as OrderId 8 end 9 go 10 create procedure [Chapter6].[UpdateOrder] 11 (@CustomerName varchar(50),@OrderDate date,@IsDeleted bit, 12 @Amount decimal, @OrderId int) 13 as 14 begin 15 update chapter6.WebOrder set CustomerName = @CustomerName, 16 OrderDate = @OrderDate,IsDeleted = @IsDeleted,Amount = @Amount 17 where OrderId = @OrderId 18 end 19 go 20 create procedure [Chapter6].[DeleteOrder] 21 (@OrderId int) 22 as 23 begin 24 delete from Chapter6.WebOrder where OrderId = @OrderId 25 end
2、右键设计器,并选择Update Model from Database(从数据库更新模型)。在更新向导中,选择存储过程InsertOrder,UpdateOrder和DeleteOrder。
3、选择WebOrder实体,并查看Mapping Details window(映射详细信息窗口)。单击Map Entity to Function(映射实体到函数)按钮。这个按钮是在映射详细信息窗口左边最下边的一个按钮。映射Insert、Update和Delete动作到存储过程。prperty/parameter(属性/参数)映射会自动填入。然而,存储过程InsertOrder的返回值必须映射到OrderId属性。这是实体框架用于在插入操作后获取标识列OrderId值的途径。正确映射如图6-14所示。
图6-14 存储过程/动作映射详细信息
4、在映射详细信息窗口中选择映射表(最上边的按钮)。删除WebOrder表的映射。我们将在后面使用QueryView来映射;
在解决方案浏览器中右键.edmx文件,选择Open With(打开方式) ➤XML Editor(XML文本编辑器)。在C-S映射层,将代码清单6-28中的查询视图(QueryView)插入到标签<EntitySetMapping>中,QueryView将映射实体WebOrder。
小心!C-S映射层的修改会在下一次从数据库更新模型时丢失。
代码清单6-28. 使用QueryView为WebOrder表映射实体集
1 <EntitySetMapping Name="WebOrders"> 2 <QueryView> 3 select value 4 Apress.EF6Recipes.BeyondModelingBasics.Recipe10.WebOrder(o.OrderId, 5 o.CustomerName,o.OrderDate,o.IsDeleted,o.Amount) 6 from ApressEF6RecipesBeyondModelingBasicsRecipe10StoreContainer.WebOrder as o 7 where (o.OrderDate > datetime'2012-01-01 00:00') || 8 (o.OrderDate between cast('2010-01-01' as Edm.DateTime) and 9 cast('2012-01-01' as Edm.DateTime) and !o.IsDeleted) || 10 (o.Amount > 200 and o.OrderDate < 11 cast('2010-01-01' as Edm.DateTime)) 12 </QueryView> 13 </EntitySetMapping>
原理
QueryView是一种只读映射,它可以用来代替实体框架为我们提供的默认映射。当QueryView在映射层标签<EntitySetMapping>中时,它将映射存储层定义的表和在概念模型层中定义的实体。当它在标签<AssociationSetMapping>中时,它将映射存储层中的关系和概念模型中的关联。QueryView的通常用法是,用在标签<AssociationSetMapping>中,用它来实现基于条件的,且不能通过默认的条件映射来实现的继承映射。
QueryView使用Entity SQL来表示,QueryView只能查询在概念模型定义的实体。另外,QueryView中的eSQL不支持gourp by和group聚合。
当实体使用QueryView来映射时,实体框架对这精确的映射实现浑然不知。这是因为实体框架不知道底层的表和列用来创建实体的实例,它不能产生适当的存储层动作来插入、更新或者删除实体。实体被实例化后,实体框架也不会进行跟踪。它不知道底层如何修改实体。
实现插入、更新和删除动作的责任落到了开发员人的头上,这些动作可以直接在.edmx文件中实现,也可以在数据库使用存储过程来实现。为了管理这些动作的存储过程,你需要创建<ModificationFunctionMapping>节。我们在第四步使用设计器而不是直接修改.edmx文件的方式来实现的。
如果使用QueryView映射的实体与别的实体之间有关联,这些关联及其关联的实体都需要使用QueryView来映射。当然,这会变得相当麻烦。QueryView是一个强有力的工具,但当很快就会成为我们的负担。
下面是QueryView的常见使用场景:
1、定义一个不被支持的过滤条件,比如:大于,小于等等;
2、映射基于除了is null,not null或者equal to条件的继承;
3、映射一列计算后的列,或是从表中返加列的子集,或是为了改变限制或类型为Data的列,例如,让它为可空类型,将一字符串列显现为整型;
4、映射基于不同主键和外键的TPT继承映射;
5、映射存储层相同的列到概念模型中不同的类型;
6、映射多个类型到同一张表;
代码清单6-28中,QueryView里包含一个Entity SQL语句,它包含三个部分。第一个部分是select从句,使用构造函数实例化一个WebOrder实例。构造函数获取属性值的顺序跟我们在概念模型中定义的顺序一至,如代码清单6-29所示。
代码清单6-29.概念模型中WebOrder实体的定义
<EntityType Name="WebOrder"> <Key> <PropertyRef Name="OrderId" /> </Key> <Property Name="OrderId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" /> <Property Name="CustomerName" Type="varchar" Nullable="false" MaxLength="255" /> <Property Name="OrderDate" Type="datetime" Nullable="false" /> <Property Name="IsDeleted" Type="bit" Nullable="false" /> <Property Name="Amount" Type="money" /> </EntityType>
注意,在代码清单6-28中的Entity SQL中,在创建WebOrder实体的实例时,我们完全限定命名空间EFRecipesModel。我们在from从句中同样限定了存储容器EFRecipesModelStoreContainer。
在Entity SQL表达式中的最后一节,包含了where从句,它是我们这个示例使用QueryView的原因。虽然where从句可以任意的复杂,但它受上面看到的QueryView中的Entity SQL限制。
代码清单6-30演示了,在模型中插入和获取WebOrders。
代码清单6-30.插入和获取WebOrder实体
1 using (var context = new Recipe10Context()) 2 { 3 var order = new WebOrder 4 { 5 CustomerName = "Jim Allen", 6 OrderDate = DateTime.Parse("5/3/2012"), 7 IsDeleted = false, 8 Amount = 200 9 }; 10 context.WebOrders.Add(order); 11 order = new WebOrder 12 { 13 CustomerName = "John Stevens", 14 OrderDate = DateTime.Parse("1/1/2011"), 15 IsDeleted = false, 16 Amount = 400 17 }; 18 context.WebOrders.Add(order); 19 order = new WebOrder 20 { 21 CustomerName = "Russel Smith", 22 OrderDate = DateTime.Parse("1/3/2011"), 23 IsDeleted = true, 24 Amount = 500 25 }; 26 context.WebOrders.Add(order); 27 order = new WebOrder 28 { 29 CustomerName = "Mike Hammer", 30 OrderDate = DateTime.Parse("6/3/2013"), 31 IsDeleted = true, 32 Amount = 1800 33 }; 34 context.WebOrders.Add(order); 35 order = new WebOrder 36 { 37 CustomerName = "Steve Jones", 38 OrderDate = DateTime.Parse("1/1/2008"), 39 IsDeleted = true, 40 Amount = 600 41 }; 42 context.WebOrders.Add(order); 43 context.SaveChanges(); 44 } 45 46 using (var context = new Recipe10Context()) 47 { 48 Console.WriteLine("Orders"); 49 Console.WriteLine("======"); 50 foreach (var order in context.WebOrders) 51 { 52 Console.WriteLine("\nCustomer: {0}", order.CustomerName); 53 Console.WriteLine("OrderDate: {0}", order.OrderDate.ToShortDateString()); 54 Console.WriteLine("Is Deleted: {0}", order.IsDeleted.ToString()); 55 Console.WriteLine("Amount: {0:C}", order.Amount); 56 } 57 }
代码清单6-30的输出如下。注意,只有满足我们在QueryView中,使用Entity SQL表达式定义的条件的customers才被显示。
Orders... Customer: John Stevens Order Date: 1/1/2011 Is Deleted: False Amount: $400.00 Customer: Jim Allen Order Date: 5/3/2012 Is Deleted: False Amount: $200.00 Customer: Mike Hammer Order Date: 6/3/2013 Is Deleted: True Amount: $1,800.00
实体框架交流QQ群: 458326058,欢迎有兴趣的朋友加入一起交流
谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/