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*不会*真正检索数据库中的所有依赖实体并执行删除:它仅删除已经存在内存中的有依赖关系的对象。

所以如下是黄金法则:

  1. 如果你在模型中添加一个级联删除规则,你必须在数据库中有一个相应的DELETE规则。
  2. 如果由于一些原因你坚持打破规则(1),级联删除仅当你将所有依赖对象加载到内存中时才起作用。
  3. (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操作。

很容易吧。

时间: 2024-08-10 16:33:49

Entity Framework技巧系列之八 - Tip 29 – 34的相关文章

Entity Framework技巧系列之五 - Tip 16 – 19

提示16. 当前如何模拟.NET 4.0的ObjectSet<T> 背景: 当前要成为一名EF的高级用户,你确实需要熟悉EntitySet.例如,你需要理解EntitySet以便使用 AttachTo(-) 或创建EntityKey. 在大部分情况下,针对每个对象/clr类型只有一个可能的EntitySet.Tip 13正是利用这种想法来简化附加(Attach)对象并且你也可以对Add使用类似的技巧. 然而为了在.NET 4.0中解决这个问题,我们添加了一个叫做 ObjectSet<T&

Entity Framework技巧系列之六 - Tip 20 – 25

提示20. 怎样处理固定长度的主键 这是正在进行中的Entity Framework提示系列的第20篇. 固定长度字段填充: 如果你的数据库中有一个固定长度的列,例如像NCHAR(10)类型的列,当你进行一次插入时,填充会自动发生.所以例如如果你插入'12345',你将得到5个自动填充的空格,来创建一个10个字符长度的字符串. 大多数情况下,这种自动填充不会有问题.但是在使用Entity Framework时如果你使用这些列的一个作为你的主键,你可能会在进行标识识别(identity resol

Entity Framework技巧系列之七 - Tip 26 – 28

提示26. 怎样避免使用不完整(Stub)实体进行数据库查询 什么是不完整(Stub)实体? 不完整实体是一个部分填充实体,用于替代真实的对象. 例如: 1 Category c = new Category {ID = 5}; 就是一个不完整实体. 这个实体中只有ID被填充,表示这是一个代表Category 5的Stub. Stub实体什么时候有用? 当你真正不需要知道一个实体的一切对象时,Stub实体就很有用,主要因为通过使用这种实体你可以避免不必要的查询,但也因为它们比EntityKey更

Entity Framework技巧系列之十三 - Tip 51 - 55

提示51. 怎样由任意形式的流中加载EF元数据 在提示45中我展示了怎样在运行时生成一个连接字符串,这相当漂亮. 其问题在于它依赖于元数据文件(.csdl .ssdl .msl)存在于本地磁盘上. 但是如果这些文件存在于web服务器中或者类似的位置,甚至你无权访本地文件系统而无法把它们拷贝到本地呢? 原来你也可以由流中加载元数据,这篇提示将告诉你怎么做. 步骤1:获得用于CSDL,MSL与SSDL的XmlTextReaders: 这可以尽可能的简单,如'new XmlTextReader(url

Entity Framework技巧系列之十二 - Tip 46 - 50

提示46. 怎样使用Code-Only排除一个属性  这次是一个真正简单的问题,由StackOverflow上这个问题引出.  问题:  当我们使用Code-Only把一个类的信息告诉Entity Framework,默认情况下每个属性会成为Entity的一部分,并作为一个存储于数据库中的结果. 通常这是你想要的结果. 但是也有例外,考虑这个类: 1 public class Person{ 2 public int ID {get;set;} 3 public string Firstname

Entity Framework技巧系列之十一 - Tip 42 - 45

提示42. 怎样使用Code-Only创建一个动态模型 背景: 当我们给出使用Code-Only的例子,总是由创建一个继承自ObjectContext的强类型的Context开始.这个类用于引导模型. 例如这个类(处于简化问题考虑省略了属性体): 1 public class MyContext : ObjectContext 2 { 3 public ObjectSet<Category> Categories { get; } 4 public ObjectSet<Product&g

(翻译)Entity Framework技巧系列之十 - Tip 37 - 41

提示37. 怎样进行按条件包含(Conditional Include) 问题 几天前有人在StackOverflow上询问怎样进行按条件包含. 他们打算查询一些实体(比方说Movies),并且希望预先加载一个相关项目(比方说,Reviews),但又仅要那些匹配一些条件的reviews(如,Review.Stars==5). 不幸的是EF的预先加载对此没有完整的支持,如,对于 ObjectQuery<Movie>.Include(…) 方法,Include或者是全部加载或者是不加载任何东西.

Entity Framework技巧系列之二 - Tip 6 - 8

提示6. 如何及何时使用贪婪加载 什么时候你需要使用贪婪加载? 通常在你的程序中你知道对查询到的实体将要进行怎样的操作. 例如,如果你查询一个订单以便为一个客户重新打印,你知道没有组成订单的项目即产品的信息重打印将是不完整的,所以你知道你将需要同时加载这些信息. 这是贪婪加载起作用的一类场景. 如果你知道你需要额外信息,或实体,你可能也会预先加载这些实体(贪婪加载),因为这将省下生在将来的查询. 怎样进行贪婪加载? 与一些普遍存在的错误观念相反,Entity Framework中贪婪加载即可行也

Entity Framework技巧系列之十 - Tip 37 - 41

提示37. 怎样进行按条件包含(Conditional Include) 问题 几天前有人在StackOverflow上询问怎样进行按条件包含. 他们打算查询一些实体(比方说Movies),并且希望预先加载一个相关项目(比方说,Reviews),但又仅要那些匹配一些条件的reviews(如,Review.Stars==5). 不幸的是EF的预先加载对此没有完整的支持,如,对于 ObjectQuery<Movie>.Include(-) 方法,Include或者是全部加载或者是不加载任何东西.