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> 的新类。ObjectContext中表示一个EntitySet属性的属性的返回类型使用 ObjectSet<T> 替代了 ObjectQuery<T> 。ObjectSet<T>与ObjectQuery<T>不同,因为其不仅支持查询,也允许你进行Add与Attach实体。
所以不要再这样写:
1 ctx.AddObject(“Customers”, newCustomer);
这种情况下你需要以字符串(我提到过我痛恨字符串吗?)形式指定EntitySet的名称,ObjectSet<T>将允许你这样做:
1 ctx.Customers.AddObject(newCustomer);
不像提示13提出的解决方案,ObjectSet<T>甚至可以在一个对象有多于一个可能的EntitySet,又称MEST,的情况下工作。
ObjectSet<T>也有另一个非常重要的优势。其实现了IObjectSet<T>接口,这样你可以针对接口来编写代码与测试,这意味着可以很容易的伪造或模拟你的ObjectContext。
下一个版本的EF将会相当酷。
但是目前我们怎样在.NET 3.5中模拟这个特性呢?
解决方案:
通过使用扩展方法构建一种天真的解决方案*实际上非常简单。
我们仅仅向ObjectQuery<T>添加一个扩展方法使其看起来像包含一些其它方法:
1 public static void AddObject<T>(
2 this ObjectQuery<T> query, T entity
3 )
4 public static void Attach<T>(
5 this ObjectQuery<T> query, T entity
6 )
一旦我们实现了这些方法,你可以编写可以在.NET 4.0中编写的相同类型的代码:
1 ctx.Customers.Attach(oldCustomer);
2 ctx.Customers.AddObject(newCustomer);
现在为了实习这些方法,我们仅需要两个东西:
- ObjectContext,这样我们可以真正的执行添加与附加。
- 与ObjectQuery<T>关联的EntitySet的名称。
ObjectContext不值一提。有一个属性包含一个称为Context的ObjectQuery可以给我们所需的东西。
得到的EntitySet名称稍有点麻烦,关键点在于使用CommandText属性。此属性通常*就是一个字符串,看起来有些像这样:“EntitySetName”,所以我们需要做的就是去除开头的’[’与结尾的’]’,这样我们就得到EntitySet的名称。
由于所有方法都需要EntitySet的名称,我们写一个函数来获取它:
1 public static string GetEntitySetName<T>(
2 this ObjectQuery<T> query)
3 {
4 string name = query.CommandText;
5 // See Caveat!
6 if (!name.StartsWith("[") || !name.EndsWith("]"))
7 throw new Exception("The EntitySet name can only be established if the query has not been modified");
8 return name.Substring(1, name.Length - 2);
9 }
其它两个方法完全微不足道:
1 public static void AddObject<T>(
2 this ObjectQuery<T> query, T entity)
3 {
4 string set = query.GetEntitySetName();
5 query.Context.AddObject(set, entity);
6 }
7
8 public static void Attach<T>(
9 this ObjectQuery<T> query, T entity)
10 {
11 string set = query.GetEntitySetName();
12 query.Context.AttachTo(set, entity);
13 }
这样就完成了,很容易。
*说明(防误解):
我称这是一个天真的解决方案是因为有可能进行下面这样的操作:
1 context.Customer.Where(“it.Name == ‘MSFT’”).Attach(oldCustomer);
且这将会失败。这是由于上面代码段中对Where(..)的调用修改了查询的CommandText,而我们使用一个非常天真的函数由CommandText提取名称。
这个函数的整体目标仍然是易于使用,且你不太可能会写那样的代码,所以总之可能一个天真的解决方案可能刚刚好。
提示17. 怎样使用AttachAsModified(..)进行一次性更新
背景:
在提示13 – 怎样以简单方式附加这篇随笔中,我展示了怎样’确定’一个特殊CLR类型的EntitySet,以便你可以Attach它。
但是到目前为止所有提示中使用了相同的基本模式:
- 附加Entity的原始(original)版本。
- 修改它
- 保存更改(SaveChanges)
在提示13与提示16中我给出提示怎样简化第(1)步操作。在提示7与提示9中分别提到了在步骤(2)操作中你需要理解的问题。
但如果你想将步骤(1)和(2)合二为一应该怎样做呢。
例如,你已经有一个修改过的对象,并且你仅想附加它。这就是 AttachAsModified(…) 登场的地方。
解决方案:
在这一步有两个我们想要处理的核心任务:
- 附加entity(在这一步,EF认为其处于未更改状态)
- 将entity的每一个属性标记为被修改。
要完成这个,你可以使用提示13中展示的默认实体集(entity set)的思想,直接向ObjectContext添加一个扩展方法。
但取而代之的是,我将基于提示16的思想来构建,在ObjectQuery<T>上添加另一个扩展方法,如下这样:
1 public static void AttachAsModified<TEntity>(
2 this ObjectQuery<TEntity> query,
3 TEntity entity) where TEntity : EntityObject
4 {
5 if (query == null) throw new ArgumentNullException("query");
6 if (entity == null) throw new ArgumentNullException("entity");
7 // Uses method created in Tip 16
8 query.Attach(entity);
9 query.Context
10 .ObjectStateManager
11 .MarkAllPropertiesModified(entity);
12 }
Attach(..) 方法在提示16中实现,所以现在我仅需添加的是一个 MarkAllPropertiesModified(…) 实现。
虽然此实现使用了ObjectStateManager内部一些隐藏的代码,但实际上它非常的简单:
1 public static void MarkAllPropertiesModified<TEntity>(
2 this ObjectStateManager manager,
3 TEntity entity) where TEntity : EntityObject
4 {
5 if (manager== null)
6 throw new ArgumentNullException("manager");
7 if (entity == null)
8 throw new ArgumentNullException("entity");
9
10 // get the object state entry for the Entity
11 var entry = manager.GetObjectStateEntry(entity);
12
13 // use metadata to get all the property names
14 // this is quicker and safer than reflection,
15 // because it ignores properties not in the model
16 var propNames =
17 from x in entry.CurrentValues.DataRecordInfo.FieldMetadata
18 select x.FieldType.Name;
19
20 // loop over every property and mark it modified
21 foreach (var propName in propNames)
22 entry.SetModifiedProperty(propName);
23 }
这段代码所做的就是找到附加的entity的ObjectStateEntry,并遍历其所有的属性来将它们标记为被修改。
使用这些扩展方法替换,可以编写如下这样代码:
1 Customer updatedCustomer = GetUpdatedEntity();
2 ctx.Customers.AttachAsModified(updatedCustomer);
3 ctx.SaveChanges();
非常简单不是吗?
注意首次我不必要在附加代码前后实行我的更改。所有对实体有趣的更改是在将实体附加到context之前完成的。
这使得在你的代码中创建层变得更加的容易。
说明(防误解):
在.NET 3.5 SP1中由于通过普通引用或缺少外键属性会引起一些问题。
如果你想要更新引用属性(如: customer.SalesPerson ),你将需要在附加逻辑的前后重新引入一些东西。
在调用 AttachAsModified(…) 之前,你可以更新所有的属性但不包括引用。引用将需要为它们的原始值,这是由于Entity Framework处理独立关联(Independent Association)的方式。
然后在调用 AttachAsModified(…) 之后,你将需要使用最新的值更新引用,例如,这样的代码( customer.SalesPersonReference.EntityKey = … )
参见提示7获得更多关于这个话题的内容。
提示18. 怎样决定你的ObjectContext的生存期
我们收到的一个最常见的问题是一个ObjectContext应该存在多久。常提到的选项包括下面之一:
- 函数
- 表单
- 线程
- 应用程序
很多人正在询问这些类型的问题,此处一个相关的例子是Stackflow上的一个问题。我相信在我们论坛上也有更多的问题被掩盖了。
这是一个典型的it depends(这取决于)类型的问题。
大量因素用于权衡问题的决策,包括:
l 析构(Disposal):
干净的析构ObjectContext及其资源是重要的。如果你为每个函数创建一个新的ObjectContext同样是非常的容易,因为你可以简单的写一个using块来确保资源被恰当的析构:
1 using (MyContext ctx = new MyContext())
2 {
3 }
l 构造成本:
一些人们关心一次又一次重建ObjectContext对象的开销,这很容易理解。现实是这个开销实际上相当低,因为通常其只涉及以引用方式将元数据由一个全局缓存复制到新的ObjectContext。通常我不认为这个开销值得担忧,但一如既往,对那个规则将有一个例外。
l 内存使用:
你越多的使用一个ObjectContext,其将逐渐的变大。这是因为其保持一个到所有其知道的实体的引用,尤其是你查询过的,添加或附加过的任何东西。所以你应当重新考虑是否无限制的共享同一个ObjectContext。对那个规则有一些例外及一个解决方案,但是对于大多数情况这些方法只是不推荐的。
- 如果你的ObjectContext仅仅进行NoTracking查询,这样其不会变大,因为ObjectContext立即忘掉这些实体。
- 你可以实现一些非常明确的清理逻辑,如,实现一些类型的回收接口,实现这些接口会遍历ObjectStateManager来分离实体并 AcceptingChanges(..) 来丢弃已删除的对象。注意:我不推荐这样做,我只是说这样做可能,我不知道如果相比较消遣是否这会导致任何的节省。所以这可能是将来一篇文章的一个很好的主题。
l 线程安全:
如果你正试图重用一个ObjectContext,你应该明白那不是线程安全的,例如,类似于标准.NET集合类。如果你由许多线程(如,web请求)访问它,你讲需要手动确保你的同步访问。
l 无状态:
如果服务被设计为无状态,就像大多数web服务应该的那样,在请求之间重用一个ObjectContext可能不是最佳方案。因为上次调用在ObjectContext中留下的东西可能对你的应用产生你不期望看到的微妙的影响。
l 自然限制的生存期:
如果你有一个自然限制生存期的方法来管理ObjectContext,如一个短暂存在的表单,一个UnitOfWork或一个Repository,这样将ObjectContext限定在相应的范围内可能是要做的最好的处理。
正如你看到的有很多问题在起作用。
大多数这些问题都可以通过使用一个不被共享的短暂生存期的context来解决。
所以那就是我推荐的实用方法。
然而,始终理解’实用方法’背后的原因将帮助你了解什么时候最适合“走自己的路”。
提示19 – 怎样在Entity Framework中使用乐观并发
这是正在进行中的Entity Framework提示系列的第19篇。
背景:
如果你有一个含有timestamp列的表,通过逆向工程由这个表生成一个实体,最终在实体中将有一个Binary类型的属性(在我的例子中称为Version)。
如果你查看属性窗口中那个属性,你讲看到如下这样的东西:
此处有趣的事情是并发模式(Concurrency Mode)。Entity Framework支持2中并发模式:
- None – 这是默认值,意味着这个属性不会参与任何并发检查
- Fixed – 这意味着此属性的原始值将被作为所有update或delete的WHERE子句的一部分
由图可知timestamp的并发模式为Fixed,这意味着由数据库原始值会被包含在任何Update或Delete的WHERE子句中。
如果你更深入一步,在XML编辑器中打开EDMX你可以看到Storage Model(亦称SSDL),你将注意到一些其他的东西,即,Version属性有一个值为Computed的StoreGeneratedPattern属性。
StoreGeneratedPattern有3个可能的值:
- None – 这是默认值且显然是最常见的。这意味着此列在数据库中不被生成。
- Identity – 这意味着当EF进行数据库插入时将生成一个值。所以在一次插入后,EF将获取这个生成的值并将其填充入Entity返回。这个设置频繁用于在数据库中自动生成的主键。
- Computed – 这意味着无论EF进行插入或更新时,数据库都将生成一个新值。在插入与更新后,EF将生成值填充入实体并返回。正如你猜到的这一般用于如时间戳(Timestamp)之类的东西。
处理乐观并发异常:
在下面的例子中,我有一个称为Post的实体,其中包含一个称为Version的时间戳列。给出下面这段简单的代码:
1 using (TipsEntities ctx1 = new TipsEntities())
2 {
3 // Get a post (which has a Version Timestamp) in one context
4 // and modify.
5 Post postFromCtx1 = ctx1.Post.First(p => p.ID == 1);
6 postFromCtx1.Title = "New Title";
7
8 // Modify and Save the same post in another context
9 // i.e. mimicking concurrent access.
10 using (TipsEntities ctx2 = new TipsEntities())
11 {
12 Post postFromCtx2 = ctx2.Post.First(p => p.ID == 1);
13 postFromCtx2.Title = "Newer Title";
14 ctx2.SaveChanges();
15 }
16 // Save the changes... This will result in an Exception
17 ctx1.SaveChanges();
18 }
…将引发一个OptimisticConcurrencyException:
…现在如果我们进一步深入,点击’查看详细’你将看到OptimisticConcurrencyException通过StateEntries属性允许你访问与引起并发异常的实体相关的ObjectStateEntry(s):
这意味着如果你想要优雅的处理此类情况,你只需简单的捕获OptimisticConcurrencyException并获取与那些StateEntries相关的实体,一边你可以提供一些类型的信息:
1 catch (OptimisticConcurrencyException ex) {
2 ObjectStateEntry entry = ex.StateEntries[0];
3 Post post = entry.Entity as Post;
4 Console.WriteLine("Failed to save {0} because it was changed in the database", post.Title);
5 }
很简单,如果你问我。
当然现实中的事情这么简单的很少。你的需求可能是你不得不给用户补偿失败操作并重试的能力。你该怎样正确的完成呢?
嗯,这确是一个有趣的场景,所以期待即将到来的另一个提示。
Entity Framework技巧系列之五 - Tip 16 – 19的更多相关文章
- 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 29 – 34
提示29. 怎样避免延迟加载或Load()阅读器问题 如果你有如下这样的代码: 1 var results = from c in ctx.Customers 2 where c.SalesPerso ...
- 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 46 - 50
提示46. 怎样使用Code-Only排除一个属性 这次是一个真正简单的问题,由StackOverflow上这个问题引出. 问题: 当我们使用Code-Only把一个类的信息告诉Entity F ...
- (翻译)Entity Framework技巧系列之十 - Tip 37 - 41
提示37. 怎样进行按条件包含(Conditional Include) 问题 几天前有人在StackOverflow上询问怎样进行按条件包含. 他们打算查询一些实体(比方说Movies),并且希望预 ...
- Entity Framework技巧系列之十四 - Tip 56
提示56. 使用反射提供程序编写一个OData Service 在TechEd我收到一大堆有关将数据作为OData暴露的问题. 到目前为止你大概知道可以使用数据服务与Entity Framework将 ...
随机推荐
- 记一次-angular-数字格式化
一个收费功能模块,需要做数据验证. input标签的ng-model的数据格式化 <input type="number" class="form-control& ...
- centsOS下安装vsftp的配置
1. 添加用户组 # groupadd www 2. 修改配置 # vi /etc/vsftpd/vsftpd.conf 查找: #chroot_list_enable=YES #chroot_lis ...
- cloudeye的实现
难点在于DNS server的搭建,然而非常简单,安装dnslib就有DNS server:zoneresolver.py,稍作修改即可使用 # -*- coding: utf-8 -*- fro ...
- Sass与Compress实战:第八章
概要:帮助你实现样式表的最佳性能 本章内容: ● 样式表拼接 ● 样式表和资源压缩 ● 减少和并行图片请求的策略 ● 选择器性能和优化策略 1. 测量客户端性能 性能优化的起点和终点都是测量.在第一次 ...
- GlusterFS缺点分析[转]
原文:http://blog.sina.com.cn/s/blog_6b89db7a0101gbcy.html GlusterFS(GNU ClusterFile System)是一个开源的分布式文件 ...
- linshi_temp_erweima_html
<!doctype html><html><head><meta charset="utf-8"><meta content= ...
- discuz使用总结
使用xampp作为运行环境 xampp的初始目录. xampp中mysql root账户的密码是空
- C++ 内存分析-valgrind
valgrind包括了以下几个比较重要的模块:memcheck, cachegrind, callgrind, helgrind, drd, massif, dhat, sgcheck, bbv. 还 ...
- Zmodem协议
Zmodem文件传输协议 做zeppelin测试时,自己安装了虚拟机,发现一个在linux和windows之间特别方便的命令行rz/sz工具. Install # 由于虚拟机不能上网,所以先挂载镜像. ...
- 朋友遇到过的t厂面试题
朋友遇到过的t面试题 leetcode160 找链表交点 leetcode206 反转链表