Entity Framework技巧系列之八 - Tip 29 – 34
提示29. 怎样避免延迟加载或Load()阅读器问题
如果你有如下这样的代码:

1 var results = from c in ctx.Customers
2 where c.SalesPerson.EmailAddress == “…”
3 select c;
4 foreach(var customer in results)
5 {
6 Console.WriteLine(customer.Name);
7 if (IsInteresting(customer))
8 {
9 customer.Orders.Load();
10 foreach(var order in customer.Orders)
11 {
12 Console.WriteLine(“\t” + order.Value);
13 }
14 }
15 }

这段代码将会打开2个同步的阅读器。一个枚举Customers,另一个枚举当前Customer的Orders。并且仅当 Multiple Active ResultSets(又称MARS)启用时才可用。所以如果MARS未启用你讲得到一个令人不快的异常。
注意:你可能会疑问为什么我要在这里调用 IsInteresting(..) 。因为如果没有这个判断,这种模式的代码是明确不推荐的。如果可以避免你不应该这样在循环中访问数据库,换句话说,如果你预先知道需要所有Customer的Order,你应该使用Include()来预先加载订单。
启用MARS很容易,只需将连接字符串中Multiple Active ResultSets=true;即可。
一般情况下不需要自己做,因为大多数连接字符串都是EF设计器创建的,其会为你进行这个设置。在3.5数据库优先及4.0模型优先的情况下都是如此。
但是如果"你"提供了ConnectionString,如Code Only中那样,你需要记得开启MARS。
因为这对3.5和4.0都有效,所以如果出错这两者机会也是均等的。
但是在4.0错误可能更隐蔽,因为新的LazyLoading特性(之前称作DeferredLoading)。
总之,问题的主旨是记得打开MARS!
提示30. 怎样使用自定义数据库函数(UDF)
想象你有一个像Nerd Dinner中DistanceBetween函数这样的数据库函数:

1 CREATE FUNCTION [dbo].[DistanceBetween](
2 @Lat1 as real,
3 @Long1 as real,
4 @Lat2 as real,
5 @Long2 as real)
6 RETURNS real
7 AS
8 BEGIN
9 …
10 END

你想在Entity Framework使用它
声明这个函数
第一步在XML编辑器中打开EDMX文件,在<edmx:StorageModels>元素中<Schema>下添加一个<Function>元素。

完成后应该如下:

1 <Function Name="DistanceBetween"
2 IsComposable="true"
3 Schema="dbo"
4 Aggregate="false"
5 BuiltIn="false"
6 ReturnType="float">
7 <Parameter Name="Lat1" Type="float" Mode="In"/>
8 <Parameter Name="Long1" Type="float" Mode="In"/>
9 <Parameter Name="Lat2" Type="float" Mode="In"/>
10 <Parameter Name="Long2" Type="float" Mode="In"/>
11 </Function>

在eSQL中使用函数
现在可以在eSQL中调用这个函数:
1 SELECT VALUE(D) FROM MyModel.Dinners AS D
2 WHERE StorageNamespace.DistanceBetween(
3 D.Latitude,D.Longitude,-34,174) < 50
MyModel就是你EntityContainer的名称(通常与ObjectContext相同),StorageNamespace是你的存储模型模式的命名空间。
在LINQ中使用函数
大部分人不使用eSQL,所以你可能会疑问怎样在LINQ中使用?
在3.5SP1中如下这样:
1 var nearbyDinners =
2 from d in ctx.Dinners.Where(
3 “StorageNamespace.DistanceBetween(it.Latitude, it.Longitude, –34, 174) < 50”
4 ) select d;
这里我们通过一个查询构造方法混合使用LINQ与eSQL,这个方法接收一个eSQL片段,在eSQL中调用了数据库函数。注意代码段通过'it'关键字关联到当前项。如果需要甚至可以关联到参数。
这很赞。
但是如果没有字符串会更好。
EF 4.0中的改进
在EF 4.0中你可以编写下面这样的代码来代替:
1 var nearbyDinners =
2 from d in ctx.Dinners
3 where DistanceBetween(d.Latitude, d.Longitude, –34,174) < 50
4 select d;
这看起来更好。没有上面那样的字符串并且支持编译时检查。
你需要一个这样的方法来使上面的代码工作:

1 [EdmFunction("StorageNamespace", "DistanceBetween")]
2 public double DistanceBetween(
3        double lat1,
4        double long1,
5        double lat2,
6        double long2)
7 {
8    throw new NotImplementedException("You can only call this method as part of a LINQ expression");
9 }

你可能会疑问为什么这个方法抛出一个异常?
我们从不真正需要直接执行这个方法。我们仅仅用它来编写LINQ查询,此查询会被翻译为SQL而不是实际调用这个方法。
EF使用EdmFuncation特性来得知哪个数据库函数需要代替这个函数被调用。
很酷吧。
好好享受。
提示31. 怎样组合L2O(LINQ to Objects)与L2E(LINQ to Entities)查询
考虑你想写一个如下这样的查询:
1 var possibleBuyers=
2 from p in ctx.People
3 where p.Address.City == “Sammamish” && InMarketForAHouse(p)
4 select p;
理论上只要InMarketForAHouse可以翻译为SQL这段代码就可以执行。
在EF4.0中可以通过为需要的Model或数据库函数创建一个CLR stub来实现。
假设如果没有对应的SQL。
可能这个功能需要使用所有那些不属于数据库的东西。
现在你不得不对查询进行“分割”。例如,将查询分割为基础的LINQ to Entities查询和依赖于L2E的LINQ to Objects查询。
你可能会尝试下如下这样的代码:
1 var partialFilter = from p in ctx.People
2 where p.Address.City == “Sammamish”
3 select p;
4 var possibleBuyers = from p in partiallyFilter
5 where InMarketForAHouse(p);
6 select p;
但是这几乎对代码的行为没有任何作用。 IQueryable(ctx.People) 仍然会被要求将 InMarketForAHouse(..) 翻译为SQL。
你需要调用 AsEnumerable() 方法,其可以有效地将查询独立为两部分:
1 var possibleBuyers = from p in partiallyFilter.AsEnumerable()
2 where InMarketForAHouse(p);
3 select p;
AsEnumerable() 确保LINQ to Objects处理所有的随后的请求。所以LINQ to Entities提供程序(如,ctx.People)用户不会知道 InMarketForAHouse() 方法。
当然有一些警告。
虽然最终的查询可能仅迭代一小部分记录,而实际发送到数据库的查询可能返回大量数据。
所以你需要考虑这将会发生什么。
问自己这样的问题:我会由数据库得到多少数据?
你甚至可能认为“迭代”大量数据没有问题。
问题是默认下你不仅仅在迭代记录。ObjectContext也为每个Entity进行标识识别,包括那些在后续LINQ to Objects中被丢弃的实体,这相当耗资源。
这个特定的问题可以“简单”的使用一个NoTracking查询来避开。
但是这又导致另一系列问题,你不能更新结果集,除非附加它们。
总之希望下一次你需要“分割”查询时你可以更多知道怎样权衡利弊。
提示32. 怎样由SSDL创建一个数据库 – 仅EF4.0
最近我们发布了一个扩展EF4 Beta 1的包含Code Only特性的CTP版本。
你可以在这里,这里与这里了解更多关于Code Only的信息。
如果你查看Code Only的代码走查,你将看到类似下面的代码:

1 // Create a builder and configure it
2 var builder = new ContextBuilder<MyContext>();
3 …
4 // Create a context
5 var mycontext = builder.Create(sqlConnection);
6 // Prepare the Context
7 if (!myContext.DatabaseExists())
8 myContext.CreateDatabase();

CreateDatabase() , DropDatabase() , DatabaseExists() 与 CreateDatabaseScripts() 均为发布于Code Only程序集的扩展方法。
这就是很优雅的事情:这些扩展方法与Code Only的剩余部分正相交。
你可以在任何ObjectContext中使用这些扩展方法,而不管其是否由Code-Only创建。
所以你可以在*任何*ObjectContext上调用这些方法。
想象这个场景:你团队中其他人签入一个EDMX作为项目的一部分,但是当你签出后你发现没有数据库脚本。现在你使用Code-Only来创建一个本地数据库。
这些着眼于数据库模型,例如生成并执行DDL,的方法在ObjectContext.MetadataWorkspace中有描述。
伴随这些提示总是有一些警告*:
1. 当前这仅工作于EF4 Beta1。当它们终止的时候我们将发布Code Only的新版本以与EF4较新的版本一同工作。
2. CreateDatabase()不知道怎样处理存储模型中的所有东西。例如如果你的EDMX引用到数据库视图或存储过程,Code Only将不知道怎样生成等效的数据库对象。
3. 当前这仅可与SQL Server一起工作。我们有计划给Code-Only添加提供程序模型,但是那还未实现。
尽管存在这些限制,毫无疑问CreateDatabase()与它同伴们会很有用。
编码愉快!
*没有警告这就不是一个提示了
提示33. 在EF中级联删除真正如何工作
考虑在数据库中你基于一个外键关系实现级联删除。
如下:

这个删除规则表名当一个Category被删除时所有相关的Product也被删除。
如果你由数据库生成一个EF模型,你得到的模型与表面看起来与一般的没有什么不同:

但如果你深入XML的CSDL部分,你会看到:

1 <Association Name="FK_Products_Categories">
2 <End Role="Categories" Type="TipsModel.Store.Categories" Multiplicity="1">
3 <OnDelete Action="Cascade" />
4 </End>
5 <End Role="Products" Type="TipsModel.Store.Products" Multiplicity="*" />
6 <ReferentialConstraint>
7 <Principal Role="Categories">
8 <PropertyRef Name="ID" />
9 </Principal>
10 <Dependent Role="Products">
11 <PropertyRef Name="CategoryID" />
12 </Dependent>
13 </ReferentialConstraint>
14 </Association>

注意 <OnDelete> 元素,其通知EF,当一个Category被删除时,也*将*删除相关的Product。
我特意使用*将*而不是*应该*,因为EF不对数据库中的级联删除负责。
EF负责在调用 SaveChanges() 后维持ObjectContext的正确。所以EF常识同步ObjectContext到数据库完成预期的级联删除后预期的状态。
关于这个问题的存在一个说法,如果你打开如SqlProfiler之类的工具,你将注意到当一个主要元素被删除时,EF会在它知道的(如,那些被载入ObjectContext的)依赖主元素的实体上触发DELETE请求。
本质上会发生的是Entity Framework认为在删除数据库中主元素时将删除数据库中所有依赖主元素的东西。所以这就产生问题,什么呢,一个多余的DELETE来请求自己,导致已经加载相关对象被由ObjectContext中删除。
关键要注意的是EF*不会*真正检索数据库中的所有依赖实体并执行删除:它仅删除已经存在内存中的有依赖关系的对象。
所以如下是黄金法则:
- 如果你在模型中添加一个级联删除规则,你必须在数据库中有一个相应的DELETE规则。
- 如果由于一些原因你坚持打破规则(1),级联删除仅当你将所有依赖对象加载到内存中时才起作用。
- (2)是*不*被推荐的!!!
虽然我们尽全力使ObjectContext与数据库保持同步,但如果你有多层级联删除这种努力也会失败。
例如,如果你有如下这样的关系:
Category –> Product –> Order
删除一个Category的同时删除其中所有Product进而删除其Order。
EF可能,在极少的情况下,当你删除一个Category时无法与数据库同步。
例如,你有一个通过未加载的Product关联到一个Category的加载的Order,当你删除Category时,EF不知道应该删除Order。
这意味着Order会以unchanged状态留在ObjectContext中,尽管在数据库中其已被删除。
凡事预则立。
提示34. 怎样在EF中使用可更新视图
更新:谢谢Zeeshan指出默认情况下视图返回的实体中非空列最终会作为主键。
想象这种情况,你的数据库中有一个可更新的视图。
下一步你决定在Entity Framework中使用这个视图,所以你进一步导入这个视图。
产生的实体看起来像这样:

正如你所见,每一个属性都有个“钥匙”图标。
因为这个实体基于一个视图,EF不知道那些列组成主键,所以其假定每一个非空列都是主键的一部分。
固定主键
第一步要更改主键。在这个例子中ID是真正的主键。
可以在XML编辑器中打开EDMX,更改EntityType使其由如下这样,即每个属性都关联到<Key>:

变为这样:

一个很重要的需要注意的是,你不得不同时在EDMX的 <edmx:StorageModels> 与 <edmx:ConceptualModes> 节进行这个改动,因为两个模型需要在主键定义上达成一致。
将视图作为表对待
此刻你可以使用Entity Framework来查询Employees。
但是Entity Framework不允许你进行更新。
对于这个问题的一般方法是创建一个存储过程并以函数方式来使用它们。
但是考虑到视图已经具备更新的能力,以上方案显然不是很理想。
幸运的是有一个替代方案:简单的是EF认为此视图就是一个表。
这需要你更改EntitySet中StorageModel的定义。一般情况下开始时看起来像这样:

1 <EntitySet Name="Employees"
2 EntityType="Tip34Model.Store.Employees"
3 store:Type="Views"
4 store:Schema="dbo"
5 store:Name="Employees">
6 <DefiningQuery>SELECT
7 [Employees].[ID] AS [ID],
8 [Employees].[Firstname] AS [Firstname],
9 [Employees].[Surname] AS [Surname],
10 [Employees].[Email] AS [Email]
11 FROM [dbo].[Employees] AS [Employees]
12 </DefiningQuery>
13 </EntitySet>

为了让其可以被作为表对待,替换为如下这样:
1 <EntitySet Name="Employees"
2 EntityType="Tip34Model.Store.Employees"
3 store:Type="Tables"
4 Schema="dbo" />
现在你可以执行任何CRUD操作。
很容易吧。
Entity Framework技巧系列之八 - Tip 29 – 34的更多相关文章
- Entity Framework技巧系列之五 - Tip 16 – 19
		提示16. 当前如何模拟.NET 4.0的ObjectSet<T> 背景: 当前要成为一名EF的高级用户,你确实需要熟悉EntitySet.例如,你需要理解EntitySet以便使用 At ... 
- Entity Framework技巧系列之六 - Tip 20 – 25
		提示20. 怎样处理固定长度的主键 这是正在进行中的Entity Framework提示系列的第20篇. 固定长度字段填充: 如果你的数据库中有一个固定长度的列,例如像NCHAR(10)类型的列,当你 ... 
- Entity Framework技巧系列之七 - Tip 26 – 28
		提示26. 怎样避免使用不完整(Stub)实体进行数据库查询 什么是不完整(Stub)实体? 不完整实体是一个部分填充实体,用于替代真实的对象. 例如: 1 Category c = new Cate ... 
- Entity Framework技巧系列之三 - Tip 9 – 12
		提示9. 怎样直接删除一个对象而无需检索它 问题 最常见的删除Entity Framework中实体的方式是将你要删除的实体传入Context中并像如下这样删除: 1 // 按ID查找一个类别 2 / ... 
- Entity Framework技巧系列之一 - Tip 1 - 5
		提示1. 在Entity Framework中怎样排序关系(Relationships) 问题: 在Entity Framework论坛中常会看到关于排序相关联项目的问题. 例如,想象你要查询客户,并 ... 
- Entity Framework技巧系列之十三 - Tip 51 - 55
		提示51. 怎样由任意形式的流中加载EF元数据 在提示45中我展示了怎样在运行时生成一个连接字符串,这相当漂亮. 其问题在于它依赖于元数据文件(.csdl .ssdl .msl)存在于本地磁盘上. 但 ... 
- Entity Framework技巧系列之十四 - Tip 56
		提示56. 使用反射提供程序编写一个OData Service 在TechEd我收到一大堆有关将数据作为OData暴露的问题. 到目前为止你大概知道可以使用数据服务与Entity Framework将 ... 
- Entity Framework技巧系列之十一 - Tip 42 - 45
		提示42. 怎样使用Code-Only创建一个动态模型 背景: 当我们给出使用Code-Only的例子,总是由创建一个继承自ObjectContext的强类型的Context开始.这个类用于引导模型. ... 
- Entity Framework技巧系列之十二 - Tip 46 - 50
		提示46. 怎样使用Code-Only排除一个属性 这次是一个真正简单的问题,由StackOverflow上这个问题引出. 问题: 当我们使用Code-Only把一个类的信息告诉Entity F ... 
随机推荐
- MySQL的保留关键字,使用时尽量避免
			今天用phpmyadmin时,注意到一个提示: 列名 'update' 是一个MySQL 保留关键字. 突然意识到还是应该尽量避免这些保留关键字,也百度了一下.找到了这些关键字,列出来下 使用mysq ... 
- L2-002. 链表去重
			L2-002. 链表去重 题目链接:https://www.patest.cn/contests/gplt/L2-002 这题因为结点地址只有四位数,所以可以直接开一个10000的数组模拟内存就好了. ... 
- MyBatis学习-入门篇
			一.MyBatis 介绍 MyBatis 是支持普通的 SQL 查询,存储过程和高级映射的优秀持久层框架,可以进行更为细致的 SQL 优化,减少查询字段.几乎消除了所有的 JDBC 代码和参数的手工设 ... 
- ios获取相册图片   压缩图片
			从摄像头/相册获取图片 刚刚在上面的知识中提到从摄像头/相册获取图片是面向终端用户的,由用户去浏览并选择图片为程序使用.在这里,我们需要过UIImagePickerController类来和用户交互. ... 
- 在ASP.NET Web Forms中使用页面导出伪xls Excel表格
			将数据导出为Excel表格是比较常见的需求,也有很多组件支持导出真正的Excel表格.由于Excel能打开HTML文件,并支持其中的table元素以及p之类的文本元素的显示,所以把.html扩展名改为 ... 
- html中的a标签的target属性的四个值的区别?
			target属性规定了在何处打开超链接的文档. 如果在一个 <a> 标签内包含一个 target 属性,浏览器将会载入和显示用这个标签的 href 属性命名的.名称与这个目标吻合的框架或者 ... 
- 安卓---下拉刷新---上拉加载---解决导入library等自生成库文件失败的问题
			本文的下拉刷新以及上拉加载都是用PullToRefresh实现的,关于PullToRefresh的介绍以及源码,网上可以找到很多,本人在此不再赘述. PullToRefresh是一套实现非常好的下拉刷 ... 
- Apache Zeppelin
			介绍 用于做数据分析和可视化 一.二进制安装 1)下载二进制包 wget http://mirrors.tuna.tsinghua.edu.cn/apache/incubator/zeppelin/0 ... 
- C# 语言规范_版本5.0 (第3章 基本概念)
			1. 基本概念 1.1 应用程序启动 具有入口点 (entry point) 的程序集称为应用程序 (application).应用程序运行时,将创建新的应用程序域 (application doma ... 
- Chapter 15_1 require函数
			Lua提供了一个名为require的高层函数来加载模块,但这个函数只假设了关于模块的基本概念. 对于require而言,一个模块就是一段定义了一些值(函数或者包含函数的table)的代码. 为了加载一 ... 
