提示6. 如何及何时使用贪婪加载

什么时候你需要使用贪婪加载?

通常在你的程序中你知道对查询到的实体将要进行怎样的操作。

例如,如果你查询一个订单以便为一个客户重新打印,你知道没有组成订单的项目即产品的信息重打印将是不完整的,所以你知道你将需要同时加载这些信息。

这是贪婪加载起作用的一类场景。

如果你知道你需要额外信息,或实体,你可能也会预先加载这些实体(贪婪加载),因为这将省下生在将来的查询。

怎样进行贪婪加载?

与一些普遍存在的错误观念相反,Entity Framework中贪婪加载即可行也易行,你仅使用Include()方法如下这样启动查询即可:

1 var reprint = (from order in ctx.Orders.Include("Items.Product")
2 where order.Customer.Name == "Fred Blogs"
3 && order.Status == "Unshipped"
4 select order).First();

这个查询意思是,在每个满足查询条件的order中包含其"Items",同时每个Item中也包含其"Product".

这样的结果是,如下代码:

1 foreach (var item in reprint.Items)
2 {
3 Console.WriteLine("\t{0} {1} = ${2}",
4 item.Quantity,
5 item.Product.Name,
6 item.Cost);
7 }
8 Console.WriteLine(reprint.TotalCost);

不再需要新的查询。

注意:

不需要显式 Include("Items") ,对 Include("Items.Product") 的调用会隐式包含Items。

提示7 – 怎么在.NET 3.5 SP1中模拟外键属性

背景

如果你一直关注EF Design Blog这个团队博客,你可能已经看到近期我们宣布了一个用于.NET 4.0中EF的称为FK Associations的特性。

然而在.NET 3.5 SP1中,我们仅支持独立关联。这表示外键列不会做为可用的属性出现在实体中。相反这表示你不得不通过到其它实体的引用来建立关联。

例如,不同于Linq to SQL,下面的写法在EF中不可用:

1 product.CategoryID = 2; 

因为在product实体中没有"CategoryID"这个属性。

你不得不使用类似下面的替换方法:

1 product.Category = ctx.Categories.First(c => c.ID == 2); 

也就是你需要使用Category引用。这意味着你不得不在内存中放置一个Category对象,或者像上面例子那样通过查询获取一个。

不存在一个你可以直接设置CategoryID的属性的问题可能造成很大的麻烦,而这就是我们在.NET 4.0中添加了FK Associations与FK Properties的原因。

这看起来都不错,但Julie Lerman总习惯提醒我,现在正在使用.NET 3.5 SP1的人们怎么办?我们的新特性现在帮不上忙。

所以…

怎样在CategoryID不实际存在的情况下设置它?

这问题是个圈套。你当然实现不了这种想法。

但是你可以使用下面的代码实现同样的效果:

1 product.CategoryReference.EntityKey = new EntityKey("MyContainer.Categories", "ID", 2); 

这看起来有点复杂不是吗?所以让我们拆开分析:

1. 对于每一个像Category一样的引用"xxx",Entity Framework也生成了一个返回EntityReference类型的"xxxReference"属性。(EF中大量使用EntityReference来提供完成像保持关系一致性,及基于EntityKeys的实体查找等操作所需的设施与服务)

2. EntityReference的其中一个属性是EntityKey。当我们希望改变外键的值时我们需要将一个新的EntityKey设置到EntityKey属性。

3. 要创建一个新的EntityKey,我们需要知道如下3条:

1). 我们想要设置给"外键"的值。与前面代码段一致,这个值就是2。

2). 目标Entity所属的EntitySet的完成限定名。在这个例子中,我们的目标是一个Category,我们由"MyContainer.Categories"集来获取Category。

3). 目标实体中主键属性的名称。这个例子中Category的主键为"ID"属性。

很显然这不是你想要在你需要的任何地方书写的那种类型的代码。所以我们以这种方式来封装。以便…

怎样使用这些代码来仿造一个外键属性?

幸运的是Entity Framework为实体生成了部分类,所以你可以像这样简单的将这些逻辑放置于你自己的Product部分类中:

 1 public int CategoryID {
2 get {
3 if (this.CategoryReference.EntityKey == null) return 0;
4 else return (int) this.CategoryReference
5 .EntityKey.EntityKeyValues[0].Value;
6 }
7 set {
8 this.CategoryReference.EntityKey
9 = new EntityKey("MyContainer.Categories", "ID", value);
10 }
11 }

注意我们在get访问器中也使用了CategoryReference。这样我们的CategoryID属性仅就是一个基于CategoryReference的视图,这样如果EF在底层做了任何改变我们也无需被通知。

添加这样的代码后,之前我们想要的写法也就可以实现:

1 product.CategoryID = 2; 

这在做法在很多场景中都特别有用,例如MVC控制器与数据绑定中。

外传

这种解决方案本质上仅仅是隐藏了独立关联的一些限制的变通方案,同所有的变通方案其也存在缺陷。关键就是在优点与缺陷之间做出全面的权衡。一些关键的缺陷如下:

1. 部分类中的属性不会被Entity Framework所识别,所以你不能在LINQ查询中使用它们。

2. 由于在setter访问器中我们通过完全限定名引用了EntitySet,我们将Entity Class与EntitySet直接耦合在一起。对于大多数人这不成问题,但是当你试图在不同上下文间重用你的实体类型或使用MEST(MultipleEntitySets per Type)时这种方法不会工作。

3. 可能还有更多缺陷…当想到它们时我将陆续添加!

提示8 – 怎样使用LINQ to Entities编写"WHERE IN"形式的查询

想象你有一个人员表,你想查询其中姓氏包含于一个有趣的姓氏列表中的那部分人。在SQL中这很平常,你编写这样的查询:

1 SELECT * FROM People
2 WHERE Firstname IN ('Alex', 'Colin', 'Danny', 'Diego')

SQL中的IN等价于LINQ中的Contains

在LINQ(to objects)的世界不存在'IN',所以你需要像倒置顺序那样来使用Contains方法:

1 var names = new string[] { "Alex", "Colin", "Danny", "Diego" };
2 var matches = from person in people
3 where names.Contains(person.Firstname)
4 select person;

注意语义上我们已经由SQL中的:

value.IN(set)

变为LINQ中的

set.Contains(value)

结果是相同的。

.NET 3.5 SP1与.NET 4.0对Contains的支持

在.NET 4.0的EF中将支持 IEnumerable<T>.Contains(T t) 方法,所以在下个版本EF中你可以编写上文LINQ查询那样的查询。

不幸的是这种写法不被.NET 3.5 SP1支持:当遇到这种形式的LINQ表达式时LINQ to Entities会报错,因为其不知道怎样将对Contains的调用转换为SQL。

但是我们.NET 3.5 SP1的用户怎么办呢?他们应该怎样做?

.NET 3.5 SP1的变通方案

有一个由EF团队"大脑"之一Colin提供的变通方案。

这种变通方案的本质是你可以以如下方式来重写上面的查询:

1 var matches = from person in people
2 where person.Firstname == "Alex" ||
3 person.Firstname == "Colin" ||
4 person.Firstname == "Danny" ||
5 person.Firstname == "Diego"
6 select person;

当然这会使代码变长且编写这样的代码是痛苦的,但是它同样可以工作。

所以如果有一些工具方法可以很容易的创建这种类型的LINQ表达式,我们将很好的处理业务

而恰巧不久之前Colin在论坛上给出了他完成的帮助方法,借助他的方法,你可以如下这样编写查询:

1 var matches = ctx.People.Where(
2 BuildOrExpression<People, string>(
3 p => p.Firstname, names
4 )
5 );

这会生成一个与下面语句相同效果的查询:

1 var matches = from p in ctx.People
2 where names.Contains(p.Firstname)
3 select p;

但事实上后者一个明显的问题是其不可以在.NET 3.5 SP1下工作。

外传

如果你已经阅读至此,很好!

下面是使变通写法成为可能的功能函数:

 1 public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>(
2 Expression<Func<TElement, TValue>> valueSelector,
3 IEnumerable<TValue> values
4 )
5 {
6 if (null == valueSelector)
7 throw new ArgumentNullException("valueSelector");
8 if (null == values)
9 throw new ArgumentNullException("values");
10 ParameterExpression p = valueSelector.Parameters.Single();
11 if (!values.Any())
12 return e => false;
13 var equals = values.Select(value =>
14 (Expression)Expression.Equal(
15 valueSelector.Body,
16 Expression.Constant(
17 value,
18 typeof(TValue)
19 )
20 )
21 );
22 var body = equals.Aggregate<Expression>(
23 (accumulate, equal) => Expression.Or(accumulate, equal)
24 );
25
26 return Expression.Lambda<Func<TElement, bool>>(body, p);
27 }

此函数本质上构造了一个针对所有使用 valueSelector (i.e. p => p.Firstname) 的值断言表达式,并将这些断言进行OR连接来为这个完整的预言创建一个表达式,除此之外,我不打算试图解释这个函数。

Entity Framework技巧系列之二 - Tip 6 - 8的更多相关文章

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

    提示51. 怎样由任意形式的流中加载EF元数据 在提示45中我展示了怎样在运行时生成一个连接字符串,这相当漂亮. 其问题在于它依赖于元数据文件(.csdl .ssdl .msl)存在于本地磁盘上. 但 ...

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

    提示37. 怎样进行按条件包含(Conditional Include) 问题 几天前有人在StackOverflow上询问怎样进行按条件包含. 他们打算查询一些实体(比方说Movies),并且希望预 ...

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

    提示42. 怎样使用Code-Only创建一个动态模型 背景: 当我们给出使用Code-Only的例子,总是由创建一个继承自ObjectContext的强类型的Context开始.这个类用于引导模型. ...

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

    提示37. 怎样进行按条件包含(Conditional Include) 问题 几天前有人在StackOverflow上询问怎样进行按条件包含. 他们打算查询一些实体(比方说Movies),并且希望预 ...

  5. Entity Framework技巧系列之九 - Tip 35 - 36

    提示35. 怎样实现OfTypeOnly<TEntity>()这样的写法 如果你编写这样LINQ to Entities查询: 1 var results = from c in ctx. ...

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

    提示16. 当前如何模拟.NET 4.0的ObjectSet<T> 背景: 当前要成为一名EF的高级用户,你确实需要熟悉EntitySet.例如,你需要理解EntitySet以便使用 At ...

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

    提示20. 怎样处理固定长度的主键 这是正在进行中的Entity Framework提示系列的第20篇. 固定长度字段填充: 如果你的数据库中有一个固定长度的列,例如像NCHAR(10)类型的列,当你 ...

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

    提示46. 怎样使用Code-Only排除一个属性  这次是一个真正简单的问题,由StackOverflow上这个问题引出.  问题:  当我们使用Code-Only把一个类的信息告诉Entity F ...

  9. Entity Framework技巧系列之十四 - Tip 56

    提示56. 使用反射提供程序编写一个OData Service 在TechEd我收到一大堆有关将数据作为OData暴露的问题. 到目前为止你大概知道可以使用数据服务与Entity Framework将 ...

随机推荐

  1. mysql修改编码

    1.查看当前编码 2.设置utf8mb4编码(也可以是其他),修改my.cnf或my.ini

  2. 数据库还原失败System.Data.SqlClient.SqlError: 无法执行 BACKUP LOG,因为当前没有数据库备份

    结尾日志的问题 还原选择中去掉结尾日志就可以了 转载自:http://blog.csdn.net/aojiancc2/article/details/46316451

  3. 在SpringMVC的controller控制器中使用Servlet原生的API

    只需要在控制器的方法里添加相应的Servlet 参数即可! 支持以下参数: 新建一个controller类,部分代码如下:(省略xml配置文件) @RequestMapping("servl ...

  4. Hdu 3363 Ice-sugar Gourd(对称,圆)

    Ice-sugar Gourd Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)T ...

  5. redis 5 种数据结构

    常用命令 就DB来说,Redis成绩已经很惊人了,且不说memcachedb和tokyocabinet之流,就说原版的memcached,速度似乎也只能达到这个级别.Redis根本是使用内存存储,持久 ...

  6. 在家用机上搭建 Git https 服务器

    今天主要叙述在家里台式机的虚拟机上搭建支持 https 的 ubuntu git 服务器. 实际上,从一个用户请求家里 git 服务器代码,最终完成代码的传输,主要是通过以下的过程: 首先,从外界寻找 ...

  7. vb.net_介绍

    手打 vb.net 是 visual basic.net的简称.提到vb.net,就不能不先提一下vb(Visual Basic) Visaul Basic是windows环境学的一个简单.易学的编程 ...

  8. NodeJS学习指南

    七天学会NodeJS NodeJS基础 什么是NodeJS 有啥用处 如何安装 安装程序 编译安装 如何运行 权限问题 模块 require exports module 模块初始化 主模块 完整示例 ...

  9. Ubuntur软件安装

    Ubuntu软件安装 Falsh sudo apt-get install flashplugin-installer

  10. Java 泛型 通配符类型

    Java 泛型 通配符类型 @author ixenos 摘要:限定通配符类型.无限定通配符类型.与普通泛型区别.通配符捕获 通配符类型 通配符的子类型限定(?都是儿孙) <? extends ...