提示29. 怎样避免延迟加载或Load()阅读器问题
提示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操作。
很容易吧。
提示29. 怎样避免延迟加载或Load()阅读器问题的更多相关文章
- 29 GroupSock(NetAddressList)——live555源码阅读(四)网络
29 GroupSock(NetAddressList)——live555源码阅读(四)网络 29 GroupSock(NetAddressList)——live555源码阅读(四)网络 简介 Net ...
- 自定义日志阅读器——包括了一个load取Tomcat日志的分析器
最近在写往公司产品里添加Tomcat适配器,以支持Tomcat.有一些功能需要摘取到Tomcat的部分日志.没有合适的工具,也不想去网上找了,就自己写了一个. 简单的画了一下设计方案: 下面直接上代码 ...
- SqlDataReader阅读器关闭时尝试调用 HasRows 无效
SqlDataReader阅读器关闭时尝试调用 HasRows 无效 原创长白山上放羊娃 发布于2018-07-25 00:29:27 阅读数 538 收藏 展开 在SqlHelper中封装好的Sq ...
- C#与数据库访问技术总结(十二)数据阅读器(DataReader)2
遍历数据阅读器中的记录 当ExecuteReader方法返回DataReader对象时,当前光标的位置在第一条记录的前面. 必须调用阅读器的Read方法把光标移动到第一条记录,然后,第一条记录将变成当 ...
- Android 上的代码阅读器 CoderBrowserHD 修改支持 go 语言代码
我在Android上的代码阅读器用的是 https://github.com/zerob13/CoderBrowserHD 改造的版本,改造后的版本我放在 https://github.com/ghj ...
- 福昕阅读器drm加密解密总结
drm是数字版权保护的一种方式,前一段时间在做四川文轩数字图书馆项目的时候用到了相关的知识,感觉这东西对于一些在线阅读和视频播放还是有很大用处的. 对于其工作原理我也很好奇,先摘抄度娘的内容如下,当然 ...
- 开发《WORD阅读器》小结(1)
这是一个相对原生的APP开发, 从GITHUB上下载了相应的框架,应该是至少2年以前的一个lovereader,是我能快速找到的为数不多的WORD阅读器的源码.应该是用ECLIPSE开发的,而我现在只 ...
- Adobe阅读器漏洞(adobe_cooltype_sing)学习研究
实验环境:Kali 2.0+Windows XP sp3+Adobe Reader 9.0.0 类别:缓冲区溢出 描述:这个漏洞针对Adobe阅读器9.3.4之前的版本,一个名为SING表对象中一个名 ...
- pdf阅读器开发
文章基于sumatrapdf的实现(当中mupdf中的内容不会太多涉及).以及自己在此基础上做的 优化,扩展.详细效果能够參考百度阅读器精简版. 最NB的还是得属于foxit.渲染速度一流,展示大图片 ...
随机推荐
- MVC5+EF6+BootStrap3.3.5 博客系统之EF(一)
- android应用activity中调出输入法后界面调整问题的解决
在自己写的一个小应用中发现一个问题,当调出输入法后界面最下方的一个按钮被挤到了输入法的上面,这样很不美观,所以找了一下解决办法记录如下: 在AndroidManifest.xml文件中找到对应的act ...
- Automotive Security的一些资料和心得(1):Security Engineering
陆续更新一些最近在Automotive Security方面的资料和心得. 1. Overview 1.1. Software Engineering Process PLC-Phases: Intr ...
- Java 类加载机制 ClassLoader Class.forName 内存管理 垃圾回收GC
[转载] :http://my.oschina.net/rouchongzi/blog/171046 Java之类加载机制 类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指 ...
- C#中结构体与字节流互相转换
1.定义与C++对应的C#结构体 在c#中的结构体不能定义指针,不能定义字符数组,只能在里面定义字符数组的引用. C++的消息结构体如下: //消息格式 4+16+4+4= 28个字节 struct ...
- uva 10940
数学 打了个表 找一下规律.... #include <cstdio> int a[30]; void init() { a[1]=2;a[2]=4;a[3]=8;a[4]=16;a[5] ...
- C++函数中那些不可以被声明为虚函数的函数
转自C++函数中那些不可以被声明为虚函数的函数 常见的不不能声明为虚函数的有:普通函数(非成员函数):静态成员函数:内联成员函数:构造函数:友元函数. 1.为什么C++不支持普通函数为虚函数? 普通函 ...
- console中应用MFC类的方法
1.添加#include <afx.h>或者<afxwin.h> 这时会报错1>c:\program files\microsoft visual studio 8\vc ...
- eclipse查看jar包中class的中文注释乱码问题的解决
1,问题来源是在eclipse中直接查看springside的class(由eclipse自动反编译)里面注释的乱码问题: Preferences-General-Workspace-Text fil ...
- 李洪强iOS开发拓展篇—UIDynamic(重力行为+碰撞检测)
iOS开发拓展篇—UIDynamic(重力行为+碰撞检测) 一.重力行为 说明:给定重力方向.加速度,让物体朝着重力方向掉落 1.方法 (1)UIGravityBehavior的初始化 - (inst ...